@directivegames/genesys.sdk 3.2.2 → 3.2.4

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 (82) hide show
  1. package/README.md +60 -60
  2. package/dist/src/core/cli.js +22 -22
  3. package/dist/src/templates/scripts/genesys/genesys-mcp.js +25 -25
  4. package/dist/src/templates/scripts/genesys/mcp/editor-functions.js +4 -4
  5. package/dist/src/templates/src/templates/vehicle/src/ui-hints.js +30 -30
  6. package/package.json +176 -176
  7. package/scripts/post-install.ts +143 -143
  8. package/src/asset-pack/.gitattributes +88 -88
  9. package/src/asset-pack/eslint.config.js +45 -45
  10. package/src/asset-pack/gitignore +11 -11
  11. package/src/asset-pack/scripts/postinstall.ts +81 -81
  12. package/src/asset-pack/tsconfig.json +33 -33
  13. package/src/templates/.cursor/mcp.json +20 -20
  14. package/src/templates/.cursorignore +2 -2
  15. package/src/templates/.gitattributes +88 -88
  16. package/src/templates/.vscode/settings.json +6 -6
  17. package/src/templates/AGENTS.md +86 -86
  18. package/src/templates/README.md +24 -24
  19. package/src/templates/eslint.config.js +45 -45
  20. package/src/templates/gitignore +11 -11
  21. package/src/templates/index.html +34 -34
  22. package/src/templates/pnpm-lock.yaml +3676 -3676
  23. package/src/templates/scripts/genesys/build-project.ts +51 -51
  24. package/src/templates/scripts/genesys/calc-bounding-box.ts +272 -272
  25. package/src/templates/scripts/genesys/common.ts +46 -46
  26. package/src/templates/scripts/genesys/const.ts +9 -9
  27. package/src/templates/scripts/genesys/dev/dump-default-scene.ts +11 -11
  28. package/src/templates/scripts/genesys/dev/generate-manifest.ts +146 -146
  29. package/src/templates/scripts/genesys/dev/launcher.ts +46 -46
  30. package/src/templates/scripts/genesys/dev/storage-provider.ts +229 -229
  31. package/src/templates/scripts/genesys/dev/update-template-scenes.ts +84 -84
  32. package/src/templates/scripts/genesys/doc-server.ts +16 -16
  33. package/src/templates/scripts/genesys/genesys-mcp.ts +526 -526
  34. package/src/templates/scripts/genesys/mcp/doc-tools.ts +86 -86
  35. package/src/templates/scripts/genesys/mcp/editor-functions.ts +151 -151
  36. package/src/templates/scripts/genesys/mcp/editor-tools.ts +73 -73
  37. package/src/templates/scripts/genesys/mcp/get-scene-state.ts +35 -35
  38. package/src/templates/scripts/genesys/mcp/run-subprocess.ts +30 -30
  39. package/src/templates/scripts/genesys/mcp/search-actors.ts +858 -858
  40. package/src/templates/scripts/genesys/mcp/search-assets.ts +380 -380
  41. package/src/templates/scripts/genesys/mcp/utils.ts +281 -281
  42. package/src/templates/scripts/genesys/misc.ts +42 -42
  43. package/src/templates/scripts/genesys/mock.ts +6 -6
  44. package/src/templates/scripts/genesys/place-actors.ts +179 -179
  45. package/src/templates/scripts/genesys/post-install.ts +30 -30
  46. package/src/templates/scripts/genesys/prefab.schema.json +84 -84
  47. package/src/templates/scripts/genesys/remove-engine-comments.ts +134 -134
  48. package/src/templates/scripts/genesys/run-mcp-inspector.bat +4 -4
  49. package/src/templates/scripts/genesys/storageProvider.ts +182 -182
  50. package/src/templates/scripts/genesys/validate-prefabs.ts +138 -138
  51. package/src/templates/src/index.ts +22 -22
  52. package/src/templates/src/templates/firstPerson/assets/default.genesys-scene +165 -165
  53. package/src/templates/src/templates/firstPerson/src/game.ts +39 -39
  54. package/src/templates/src/templates/firstPerson/src/player.ts +63 -63
  55. package/src/templates/src/templates/fps/assets/default.genesys-scene +9459 -9459
  56. package/src/templates/src/templates/fps/src/game.ts +39 -39
  57. package/src/templates/src/templates/fps/src/player.ts +69 -69
  58. package/src/templates/src/templates/fps/src/weapon.ts +54 -54
  59. package/src/templates/src/templates/freeCamera/assets/default.genesys-scene +165 -165
  60. package/src/templates/src/templates/freeCamera/src/game.ts +39 -39
  61. package/src/templates/src/templates/freeCamera/src/player.ts +45 -45
  62. package/src/templates/src/templates/sideScroller/assets/default.genesys-scene +121 -121
  63. package/src/templates/src/templates/sideScroller/src/const.ts +45 -45
  64. package/src/templates/src/templates/sideScroller/src/game.ts +122 -122
  65. package/src/templates/src/templates/sideScroller/src/level-generator.ts +361 -361
  66. package/src/templates/src/templates/sideScroller/src/player.ts +125 -125
  67. package/src/templates/src/templates/thirdPerson/assets/default.genesys-scene +165 -165
  68. package/src/templates/src/templates/thirdPerson/src/game.ts +39 -39
  69. package/src/templates/src/templates/thirdPerson/src/player.ts +61 -61
  70. package/src/templates/src/templates/vehicle/assets/default.genesys-scene +225 -225
  71. package/src/templates/src/templates/vehicle/src/base-vehicle.ts +145 -145
  72. package/src/templates/src/templates/vehicle/src/game.ts +43 -43
  73. package/src/templates/src/templates/vehicle/src/mesh-vehicle.ts +191 -191
  74. package/src/templates/src/templates/vehicle/src/player.ts +109 -109
  75. package/src/templates/src/templates/vehicle/src/primitive-vehicle.ts +266 -266
  76. package/src/templates/src/templates/vehicle/src/ui-hints.ts +101 -101
  77. package/src/templates/src/templates/vr-game/assets/default.genesys-scene +246 -246
  78. package/src/templates/src/templates/vr-game/src/auto-imports.ts +1 -1
  79. package/src/templates/src/templates/vr-game/src/game.ts +66 -66
  80. package/src/templates/src/templates/vr-game/src/sample-vr-actor.ts +26 -26
  81. package/src/templates/tsconfig.json +34 -34
  82. package/src/templates/vite.config.ts +52 -52
@@ -1,526 +1,526 @@
1
- // How to use:
2
- // it must be built first with: pnpm build
3
- // open cursor settings - MCP servers, refresh and make sure "genesys" is connected
4
- // and you're good to go with asking cursor to place primitives in a specified project
5
- //
6
- // for another MCP client (e.g. cline) that wants to use this, the command is: `pnpm exec tsc ./scripts/genesys/genesys-mcp.ts`
7
- import pathlib from 'path';
8
-
9
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
- import * as ENGINE from 'genesys.js';
12
- import * as THREE from 'three';
13
- import { z } from 'zod';
14
-
15
-
16
- import { ActorInfoSchema, TransformSchema } from './common.js';
17
- import { EditorIdSchema, getClientWebSocket, getCurrentScene } from './mcp/editor-functions.js';
18
- import { addEditorTools } from './mcp/editor-tools.js';
19
- import { getSceneState } from './mcp/get-scene-state.js';
20
- import { assetDescriptions, AssetType, populateAssets, searchForAssets } from './mcp/search-assets.js';
21
- import { mcpLogger } from './mcp/utils.js';
22
- import { generateCode } from './misc.js';
23
- import { addGltf, placeJsClassActor, placePrefab, placePrimitive, removeActors, updateActors } from './place-actors.js';
24
-
25
- import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
26
-
27
-
28
- // Create an MCP server
29
- const server = new McpServer({
30
- name: 'Genesys-MCP',
31
- version: '0.0.1'
32
- }, {
33
- capabilities: {
34
- logging: {} // to support sendLoggingMessage
35
- }
36
- });
37
-
38
-
39
- const classRule = `
40
- The class name should start with '${ENGINE.Prefix.GAME}' or '${ENGINE.Prefix.ENGINE}' to indicate if it is a class from game project or engine.
41
- If it starts with neither, it will be matched against all registered classes. If none is found, an error will be returned.
42
- `;
43
-
44
-
45
- function requestEditorReload(editorId?: string) {
46
- const ws = getClientWebSocket(editorId);
47
- ws.send(JSON.stringify({
48
- command: 'reload'
49
- }));
50
- }
51
-
52
- addEditorTools(server);
53
-
54
- const primitiveTypes = Object.values(ENGINE.WorldCommands.PrimitiveType);
55
-
56
- server.registerTool(
57
- 'placePrimitive',
58
- {
59
- description: 'Place 3D primitives in the specified scene.',
60
- inputSchema: {
61
- editorId: EditorIdSchema,
62
- primitiveActors: z.array(
63
- z.object({
64
- primitives: z.array(
65
- z.object({
66
- type: z.enum(primitiveTypes as [string, ...string[]])
67
- .describe(`Type of primitive to place, one of: ${primitiveTypes.join(', ')}`),
68
- transform: TransformSchema.optional().describe('Transform of the primitive relative to the actor'),
69
- color: z.union([z.array(z.number()).length(3), z.string(), z.number()]).optional().describe('Color as [r, g, b], hex string, hex number, or X11 color name'),
70
- }).strict().describe('A primitive to add to the actor.')
71
- ).describe('A list of primitives to add to the actor.'),
72
- info: ActorInfoSchema.describe('Information about the primitive actor.'),
73
- transform: TransformSchema.optional().describe('Transform of the actor in the scene'),
74
- }).strict().describe('An actor containing primitives to place.')
75
- ).describe('A list of lists of primitives to add. Each inner list represents a single actor to place.'),
76
- },
77
- },
78
- async ({ editorId, primitiveActors }) => {
79
- let actorIds: string[] = [];
80
- let sceneName: string;
81
-
82
- {
83
- using loggerContext = mcpLogger(server);
84
- try {
85
- sceneName = await getCurrentScene(editorId);
86
- actorIds = await placePrimitive({
87
- sceneName,
88
- primitiveActors: primitiveActors.map(primitiveActor => ({
89
- miscInfo: {
90
- displayName: primitiveActor.info.displayName,
91
- description: primitiveActor.info.description ?? '',
92
- },
93
- transform: {
94
- position: new THREE.Vector3(...(primitiveActor.transform?.position ?? [0, 0, 0])),
95
- rotation: new THREE.Euler(...(primitiveActor.transform?.rotation ?? [0, 0, 0])),
96
- scale: new THREE.Vector3(...(primitiveActor.transform?.scale ?? [1, 1, 1])),
97
- },
98
- primitives: primitiveActor.primitives.map(primitive => ({
99
- type: primitive.type,
100
- transform: {
101
- position: new THREE.Vector3(...(primitive.transform?.position ?? [0, 0, 0])),
102
- rotation: new THREE.Euler(...(primitive.transform?.rotation ?? [0, 0, 0])),
103
- scale: new THREE.Vector3(...(primitive.transform?.scale ?? [1, 1, 1])),
104
- },
105
- color: Array.isArray(primitive.color) ? new THREE.Color(...primitive.color) : new THREE.Color(primitive.color as (string | number)),
106
- }))
107
- }))
108
- });
109
- } catch (error) {
110
- return {
111
- isError: true,
112
- content: [{ type: 'text', text: `${error}` }]
113
- };
114
- }
115
-
116
- if (actorIds.length > 0) {
117
- requestEditorReload(editorId);
118
- }
119
- }
120
-
121
- const uuids = actorIds.join(', ');
122
- const text = `Primitives placed successfully in ${sceneName}, placed actor UUIDs are [${uuids}]`;
123
- return {
124
- content: [{ type: 'text', text }]
125
- };
126
- }
127
- );
128
-
129
- server.registerTool(
130
- 'removeActors',
131
- {
132
- description: 'Remove one or more actors from the scene by their UUIDs.',
133
- inputSchema: {
134
- editorId: EditorIdSchema,
135
- actorIds: z.array(z.string()).describe('List of actor UUIDs to remove'),
136
- },
137
- },
138
- async ({ editorId, actorIds }) => {
139
- {
140
- using loggerContext = mcpLogger(server);
141
- const sceneName = await getCurrentScene(editorId);
142
- await removeActors({ sceneName, actorIds });
143
-
144
- if (actorIds.length > 0) {
145
- requestEditorReload(editorId);
146
- }
147
- }
148
- const text = `${actorIds.length} actors removed successfully`;
149
- return {
150
- content: [{ type: 'text', text }]
151
- };
152
- }
153
- );
154
-
155
-
156
- server.registerTool(
157
- 'addGltf',
158
- {
159
- description: 'Add GLTF models to the scene.',
160
- inputSchema: {
161
- editorId: EditorIdSchema,
162
- gltfs: z.array(
163
- z.object({
164
- path: z.string().describe(`Path or URL to the GLTF model to add.
165
- Unless explicitly told by user, otherwise it should always be one of these
166
- - fetched from the result of searchAssets tool
167
- - copied from an existing path of an existing placed GLTF actor.
168
- - use a verified URL to a GLTF model
169
- Never make up a relative path to a file, because that won't be resolved by the editor.
170
- `),
171
- transform: TransformSchema.optional().describe('Transform of the GLTF model'),
172
- info: ActorInfoSchema.describe('Information about the GLTF model actor.'),
173
- }).strict().describe('A GLTF model to add.')
174
- ).describe('A list of GLTF models to add.'),
175
- },
176
- },
177
- async ({ editorId, gltfs }) => {
178
- let actorIds: string[] = [];
179
-
180
- {
181
- using loggerContext = mcpLogger(server);
182
- const sceneName = await getCurrentScene(editorId);
183
- actorIds = await addGltf({
184
- sceneName,
185
- gltfs: gltfs.map(gltf => ({
186
- path: gltf.path,
187
- transform: {
188
- position: new THREE.Vector3(...(gltf.transform?.position ?? [0, 0, 0])),
189
- rotation: new THREE.Euler(...(gltf.transform?.rotation ?? [0, 0, 0])),
190
- scale: new THREE.Vector3(...(gltf.transform?.scale ?? [1, 1, 1])),
191
- },
192
- miscInfo: {
193
- displayName: gltf.info.displayName,
194
- description: gltf.info.description ?? '',
195
- },
196
- }))
197
- });
198
-
199
- if (actorIds.length > 0) {
200
- requestEditorReload(editorId);
201
- }
202
- }
203
-
204
- const uuids = actorIds.join(', ');
205
- const text = `GLTFs added successfully, placed actor UUIDs are [${uuids}]`;
206
- return {
207
- content: [{ type: 'text', text }]
208
- };
209
- }
210
- );
211
-
212
- server.registerTool(
213
- 'placePrefab',
214
- {
215
- description: 'Place prefabs in the scene.',
216
- inputSchema: {
217
- editorId: EditorIdSchema,
218
- prefabs: z.array(
219
- z.object({
220
- path: z.string().describe('Path to the prefab to place'),
221
- transform: TransformSchema.optional().describe('Transform of the prefab'),
222
- info: ActorInfoSchema.describe('Information about the prefab actor.'),
223
- }).strict().describe('A prefab to place.')
224
- ).describe('A list of prefabs to place. Each prefab can have a transform.'),
225
- },
226
- },
227
- async ({ editorId, prefabs }) => {
228
- let actorIds: string[] = [];
229
-
230
- {
231
- using loggerContext = mcpLogger(server);
232
- const sceneName = await getCurrentScene(editorId);
233
- actorIds = await placePrefab({
234
- sceneName,
235
- prefabs: prefabs.map(prefab => ({
236
- path: prefab.path,
237
- transform: {
238
- position: new THREE.Vector3(...(prefab.transform?.position ?? [0, 0, 0])),
239
- rotation: new THREE.Euler(...(prefab.transform?.rotation ?? [0, 0, 0])),
240
- scale: new THREE.Vector3(...(prefab.transform?.scale ?? [1, 1, 1])),
241
- },
242
- miscInfo: {
243
- displayName: prefab.info.displayName,
244
- description: prefab.info.description ?? '',
245
- },
246
- }))
247
- });
248
-
249
- if (actorIds.length > 0) {
250
- requestEditorReload(editorId);
251
- }
252
- }
253
- const uuids = actorIds.join(', ');
254
- const text = `Prefabs placed successfully, placed actor UUIDs are [${uuids}]`;
255
- return {
256
- content: [{ type: 'text', text }]
257
- };
258
- }
259
- );
260
-
261
-
262
- server.registerTool(
263
- 'searchAssets',
264
- {
265
- description: `Search for assets in the project and engine directories.
266
- When the user requests to place something in the scene, unless it is clearly a primitive, always try calling this first to see if any assets are fitting the request.
267
- Before using the assets found, **always** call the \`getAssetsMetadata\` tool to get the metadata about the assets, unless the given metadata is already fetched.
268
- Here is the list of assets types, including:
269
- ${Object.entries(assetDescriptions).map(([type, description]) => `- ${type}: ${description}`).join('\n')}
270
- `,
271
- inputSchema: {
272
- acceptedTypes: z.array(z.enum(Object.values(AssetType) as [string, ...string[]])).describe('List of types of assets to search for. An empty array means all types.'),
273
- searchKeywords: z.array(z.string()).describe('List of keywords to search for in the assets. Wildcards are not supported, use multiple keywords to search for multiple terms. Use an empty array to search for all assets.'),
274
- },
275
- },
276
- async ({ acceptedTypes, searchKeywords }) => {
277
- using loggerContext = mcpLogger(server);
278
-
279
- const dirs = [`${ENGINE.PROJECT_PATH_PREFIX}/assets`, `${ENGINE.ENGINE_PATH_PREFIX}/assets`];
280
- const {assets, metadataDescription} = await searchForAssets(dirs, acceptedTypes as AssetType[], searchKeywords);
281
-
282
- const result: CallToolResult = {content: []};
283
- if (metadataDescription && Object.keys(metadataDescription).length > 0) {
284
- result.content.push(
285
- { type: 'text', text: `Here is the description of the metadata fields of the assets, per asset type:\n${JSON.stringify(metadataDescription, null, 2)}` }
286
- );
287
- }
288
-
289
- result.content.push({ type: 'text', text: `Assets:\n${JSON.stringify(assets, null, 2)}` });
290
- return result;
291
- }
292
- );
293
-
294
-
295
- server.registerTool(
296
- 'getAssetsMetadata',
297
- {
298
- description: 'Get metadata for the assets found by the `searchAssets` tool.',
299
- inputSchema: {
300
- assetPaths: z.array(z.string()).describe('List of asset paths to get metadata for. The asset paths are the keys retrieved from the result of \`searchAssets\` tool.'),
301
- },
302
- },
303
- async ({ assetPaths }) => {
304
- using loggerContext = mcpLogger(server);
305
-
306
- try {
307
- const assetsInfo = await populateAssets(assetPaths, { peek: false });
308
- const metadataDescription = assetsInfo.metadataDescription;
309
- const assets = assetsInfo.assets;
310
-
311
- const result: CallToolResult = {content: []};
312
- if (metadataDescription && Object.keys(metadataDescription).length > 0) {
313
- result.content.push(
314
- { type: 'text', text: `Here is the description of the metadata fields of the assets, per asset type:\n${JSON.stringify(metadataDescription, null, 2)}` }
315
- );
316
- }
317
-
318
- result.content.push({ type: 'text', text: `Assets:\n${JSON.stringify(assets, null, 2)}` });
319
- return result;
320
-
321
- } catch (error) {
322
- return {
323
- isError: true,
324
- content: [{ type: 'text', text: `Error getting assets metadata: ${error}` }]
325
- };
326
- }
327
- }
328
- );
329
-
330
-
331
- server.registerTool(
332
- 'placeJsClassActor',
333
- {
334
- description: 'Place a JavaScript game or engine class actor in the scene.',
335
- inputSchema: {
336
- editorId: EditorIdSchema,
337
- actors: z.array(
338
- z.object({
339
- className: z.string().describe(`The name of the JavaScript class to place. ${classRule}`),
340
- constructorParams: z.array(z.record(z.any())).optional()
341
- .describe(`Optional constructor parameters to pass to the actor class.
342
- Use the getAssetsMetadata tool to discover what parameters each actor class requires,
343
- and the parameters should be an array that corresponds to the "jsClassConstructorParams" from the result of the getAssetsMetadata tool.
344
- If the actor class has no constructor parameters, the array should be empty.
345
- `),
346
- info: ActorInfoSchema.describe('Information about the actor.'),
347
- }).strict().describe('A JavaScript class actor to place.')
348
- ).describe('A list of JavaScript class actors to place. Each actor can have a transform and constructor parameters.'),
349
- },
350
- },
351
- async ({ editorId, actors }) => {
352
- using loggerContext = mcpLogger(server);
353
-
354
- const sceneName = await getCurrentScene(editorId);
355
-
356
- const result = await placeJsClassActor({sceneName, jsClasses: actors.map(actor => ({
357
- className: actor.className,
358
- constructorParams: actor.constructorParams,
359
- actorInfo: actor.info,
360
- }))});
361
-
362
- if (result.length > 0) {
363
- requestEditorReload(editorId);
364
- }
365
-
366
- const uuids = result.join(', ');
367
- const text = `JavaScript class actors placed successfully, placed actor UUIDs are [${uuids}]`;
368
- return {
369
- content: [{ type: 'text', text }]
370
- };
371
- }
372
- );
373
-
374
-
375
- server.registerTool(
376
- 'generateTemplateCode',
377
- {
378
- description: 'Generate template code for a class of actor or component. When the user asks to create a new actor or component, always try calling this first to see if any template code is available, and iterate on the code it generates until it is correct.',
379
- inputSchema: {
380
- className: z.string().describe('The name of the class to generate template code for.'),
381
- filePath: z.string().describe(`The path to the file to generate the template code for.
382
- If it is a relative path, it will be relative to the 'src/' directory, so don't include the extra 'src/' in the path.
383
- The file name should be lower camel case, such as 'myActor.ts', or 'fancyMovementComponent.ts'.`
384
- ),
385
- baseClassName: z.string().describe(`The name of the base class to generate the template code for. ${classRule}`),
386
- },
387
- },
388
- async ({ className, filePath, baseClassName }) => {
389
- using loggerContext = mcpLogger(server);
390
-
391
- const fullFilePath = pathlib.isAbsolute(filePath) ? filePath : pathlib.join('src', filePath);
392
- let success = false;
393
- try {
394
- success = await generateCode(className, fullFilePath, baseClassName);
395
- }
396
- catch (error) {
397
- return {
398
- isError: true,
399
- content: [{ type: 'text', text: `Failed to generate code: ${error}` }]
400
- };
401
- }
402
-
403
- if (success) {
404
- return {
405
- content: [
406
- {
407
- type: 'text',
408
- text: `template code for class ${className} has been generated at ${fullFilePath}`
409
- }
410
- ]
411
- };
412
- }
413
-
414
- return {
415
- isError: true,
416
- content: [{ type: 'text', text: `Error: Template code for class ${className} has not been generated.` }]
417
- };
418
- }
419
- );
420
-
421
-
422
- server.registerTool(
423
- 'getSceneState',
424
- {
425
- description: `
426
- Get the current state of the scene to understand the state of the current scene and its actors.
427
- **DO NOT** try reading the scene file directly unless explicitly asked by the user.
428
- The first time calling this tool, always call without specifiedActors and getDetailedComponentsInfo to have an overview of the scene.
429
- And then call this tool with \`specifiedActors\`, and set \`getDetailedComponentsInfo\` to true, to get more detailed information about specific actors, including their bounding boxes info, etc.
430
- `,
431
- inputSchema: {
432
- editorId: EditorIdSchema,
433
- specifiedActors: z.array(z.string()).optional().describe('A list of actor UUIDs. If specified, actors not in this list will contain only very basic information, namely their UUID and name.'),
434
- getDetailedComponentsInfo: z.boolean().optional().describe('If true, will contain detailed information about their properties, otherwise they will contain only their UUID and name. If specifiedActors is present, only actors in this list will have detailed information.'),
435
- },
436
- },
437
- async ({editorId, specifiedActors, getDetailedComponentsInfo}) => {
438
- using loggerContext = mcpLogger(server);
439
-
440
- try {
441
- const sceneName = await getCurrentScene(editorId);
442
- const state = await getSceneState(sceneName, specifiedActors, getDetailedComponentsInfo);
443
- function roundNumbersReplacer(key: string, value: any) {
444
- if (typeof value === 'number') {
445
- return Number(value.toFixed(5));
446
- }
447
- return value;
448
- }
449
- return {
450
- content: [{ type: 'text', text: `The following is the state of the scene currently opened in the editor:\n\`\`\`json\n${JSON.stringify(state, roundNumbersReplacer)}\n\`\`\`` }]
451
- };
452
- }
453
- catch (error) {
454
- return {
455
- isError: true,
456
- content: [{ type: 'text', text: `Error getting scene state: ${error}` }]
457
- };
458
- }
459
- }
460
- );
461
-
462
- server.registerTool(
463
- 'updateActors',
464
- {
465
- description: 'Update the properties of existing actors in the scene.',
466
- inputSchema: {
467
- editorId: EditorIdSchema,
468
- actors: z.array(
469
- z.object({
470
- uuid: z.string().describe('UUID of the actor to update'),
471
- transform: TransformSchema.optional().describe('New transform of the actor'),
472
- info: ActorInfoSchema.optional().describe('New information about the actor.'),
473
- }).strict().describe('An actor to update with its new properties.')
474
- ).describe('A list of actors to update with their new properties.'),
475
- },
476
- },
477
- async ({editorId, actors}) => {
478
- using loggerContext = mcpLogger(server);
479
-
480
- const makeActorUpdateArg = (actor: typeof actors[number]) => {
481
- const args: any = {};
482
- args.uuid = actor.uuid;
483
- if (actor.transform) {
484
- args.transform = {};
485
- if (actor.transform.position) {
486
- args.transform.position = new THREE.Vector3(...actor.transform.position);
487
- }
488
- if (actor.transform.rotation) {
489
- args.transform.rotation = new THREE.Euler(...actor.transform.rotation);
490
- }
491
- if (actor.transform.scale) {
492
- args.transform.scale = new THREE.Vector3(...actor.transform.scale);
493
- }
494
- }
495
- if (actor.info) {
496
- args.actorInfo = {};
497
- if (actor.info.displayName) {
498
- args.actorInfo.displayName = actor.info.displayName;
499
- }
500
- if (actor.info.description) {
501
- args.actorInfo.description = actor.info.description;
502
- }
503
- }
504
- return args;
505
- };
506
-
507
- const sceneName = await getCurrentScene(editorId);
508
- const updatedActorsNum = await updateActors({
509
- sceneName,
510
- actorsToUpdate: actors.map(actorToUpdate => makeActorUpdateArg(actorToUpdate))
511
- });
512
-
513
- if (updatedActorsNum > 0) {
514
- requestEditorReload(editorId);
515
- }
516
-
517
- return {
518
- content: [{ type: 'text', text: `Completed, ${updatedActorsNum} actors updated` }]
519
- };
520
- }
521
- );
522
-
523
- // Start receiving messages on stdin and sending messages on stdout
524
- const transport = new StdioServerTransport();
525
- await server.connect(transport);
526
- server.server.sendLoggingMessage({level: 'info', data: 'starting up MCP server for Genesys'});
1
+ // How to use:
2
+ // it must be built first with: pnpm build
3
+ // open cursor settings - MCP servers, refresh and make sure "genesys" is connected
4
+ // and you're good to go with asking cursor to place primitives in a specified project
5
+ //
6
+ // for another MCP client (e.g. cline) that wants to use this, the command is: `pnpm exec tsc ./scripts/genesys/genesys-mcp.ts`
7
+ import pathlib from 'path';
8
+
9
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
10
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
+ import * as ENGINE from 'genesys.js';
12
+ import * as THREE from 'three';
13
+ import { z } from 'zod';
14
+
15
+
16
+ import { ActorInfoSchema, TransformSchema } from './common.js';
17
+ import { EditorIdSchema, getClientWebSocket, getCurrentScene } from './mcp/editor-functions.js';
18
+ import { addEditorTools } from './mcp/editor-tools.js';
19
+ import { getSceneState } from './mcp/get-scene-state.js';
20
+ import { assetDescriptions, AssetType, populateAssets, searchForAssets } from './mcp/search-assets.js';
21
+ import { mcpLogger } from './mcp/utils.js';
22
+ import { generateCode } from './misc.js';
23
+ import { addGltf, placeJsClassActor, placePrefab, placePrimitive, removeActors, updateActors } from './place-actors.js';
24
+
25
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
26
+
27
+
28
+ // Create an MCP server
29
+ const server = new McpServer({
30
+ name: 'Genesys-MCP',
31
+ version: '0.0.1'
32
+ }, {
33
+ capabilities: {
34
+ logging: {} // to support sendLoggingMessage
35
+ }
36
+ });
37
+
38
+
39
+ const classRule = `
40
+ The class name should start with '${ENGINE.Prefix.GAME}' or '${ENGINE.Prefix.ENGINE}' to indicate if it is a class from game project or engine.
41
+ If it starts with neither, it will be matched against all registered classes. If none is found, an error will be returned.
42
+ `;
43
+
44
+
45
+ function requestEditorReload(editorId?: string) {
46
+ const ws = getClientWebSocket(editorId);
47
+ ws.send(JSON.stringify({
48
+ command: 'reload'
49
+ }));
50
+ }
51
+
52
+ addEditorTools(server);
53
+
54
+ const primitiveTypes = Object.values(ENGINE.WorldCommands.PrimitiveType);
55
+
56
+ server.registerTool(
57
+ 'placePrimitive',
58
+ {
59
+ description: 'Place 3D primitives in the specified scene.',
60
+ inputSchema: {
61
+ editorId: EditorIdSchema,
62
+ primitiveActors: z.array(
63
+ z.object({
64
+ primitives: z.array(
65
+ z.object({
66
+ type: z.enum(primitiveTypes as [string, ...string[]])
67
+ .describe(`Type of primitive to place, one of: ${primitiveTypes.join(', ')}`),
68
+ transform: TransformSchema.optional().describe('Transform of the primitive relative to the actor'),
69
+ color: z.union([z.array(z.number()).length(3), z.string(), z.number()]).optional().describe('Color as [r, g, b], hex string, hex number, or X11 color name'),
70
+ }).strict().describe('A primitive to add to the actor.')
71
+ ).describe('A list of primitives to add to the actor.'),
72
+ info: ActorInfoSchema.describe('Information about the primitive actor.'),
73
+ transform: TransformSchema.optional().describe('Transform of the actor in the scene'),
74
+ }).strict().describe('An actor containing primitives to place.')
75
+ ).describe('A list of lists of primitives to add. Each inner list represents a single actor to place.'),
76
+ },
77
+ },
78
+ async ({ editorId, primitiveActors }) => {
79
+ let actorIds: string[] = [];
80
+ let sceneName: string;
81
+
82
+ {
83
+ using loggerContext = mcpLogger(server);
84
+ try {
85
+ sceneName = await getCurrentScene(editorId);
86
+ actorIds = await placePrimitive({
87
+ sceneName,
88
+ primitiveActors: primitiveActors.map(primitiveActor => ({
89
+ miscInfo: {
90
+ displayName: primitiveActor.info.displayName,
91
+ description: primitiveActor.info.description ?? '',
92
+ },
93
+ transform: {
94
+ position: new THREE.Vector3(...(primitiveActor.transform?.position ?? [0, 0, 0])),
95
+ rotation: new THREE.Euler(...(primitiveActor.transform?.rotation ?? [0, 0, 0])),
96
+ scale: new THREE.Vector3(...(primitiveActor.transform?.scale ?? [1, 1, 1])),
97
+ },
98
+ primitives: primitiveActor.primitives.map(primitive => ({
99
+ type: primitive.type,
100
+ transform: {
101
+ position: new THREE.Vector3(...(primitive.transform?.position ?? [0, 0, 0])),
102
+ rotation: new THREE.Euler(...(primitive.transform?.rotation ?? [0, 0, 0])),
103
+ scale: new THREE.Vector3(...(primitive.transform?.scale ?? [1, 1, 1])),
104
+ },
105
+ color: Array.isArray(primitive.color) ? new THREE.Color(...primitive.color) : new THREE.Color(primitive.color as (string | number)),
106
+ }))
107
+ }))
108
+ });
109
+ } catch (error) {
110
+ return {
111
+ isError: true,
112
+ content: [{ type: 'text', text: `${error}` }]
113
+ };
114
+ }
115
+
116
+ if (actorIds.length > 0) {
117
+ requestEditorReload(editorId);
118
+ }
119
+ }
120
+
121
+ const uuids = actorIds.join(', ');
122
+ const text = `Primitives placed successfully in ${sceneName}, placed actor UUIDs are [${uuids}]`;
123
+ return {
124
+ content: [{ type: 'text', text }]
125
+ };
126
+ }
127
+ );
128
+
129
+ server.registerTool(
130
+ 'removeActors',
131
+ {
132
+ description: 'Remove one or more actors from the scene by their UUIDs.',
133
+ inputSchema: {
134
+ editorId: EditorIdSchema,
135
+ actorIds: z.array(z.string()).describe('List of actor UUIDs to remove'),
136
+ },
137
+ },
138
+ async ({ editorId, actorIds }) => {
139
+ {
140
+ using loggerContext = mcpLogger(server);
141
+ const sceneName = await getCurrentScene(editorId);
142
+ await removeActors({ sceneName, actorIds });
143
+
144
+ if (actorIds.length > 0) {
145
+ requestEditorReload(editorId);
146
+ }
147
+ }
148
+ const text = `${actorIds.length} actors removed successfully`;
149
+ return {
150
+ content: [{ type: 'text', text }]
151
+ };
152
+ }
153
+ );
154
+
155
+
156
+ server.registerTool(
157
+ 'addGltf',
158
+ {
159
+ description: 'Add GLTF models to the scene.',
160
+ inputSchema: {
161
+ editorId: EditorIdSchema,
162
+ gltfs: z.array(
163
+ z.object({
164
+ path: z.string().describe(`Path or URL to the GLTF model to add.
165
+ Unless explicitly told by user, otherwise it should always be one of these
166
+ - fetched from the result of searchAssets tool
167
+ - copied from an existing path of an existing placed GLTF actor.
168
+ - use a verified URL to a GLTF model
169
+ Never make up a relative path to a file, because that won't be resolved by the editor.
170
+ `),
171
+ transform: TransformSchema.optional().describe('Transform of the GLTF model'),
172
+ info: ActorInfoSchema.describe('Information about the GLTF model actor.'),
173
+ }).strict().describe('A GLTF model to add.')
174
+ ).describe('A list of GLTF models to add.'),
175
+ },
176
+ },
177
+ async ({ editorId, gltfs }) => {
178
+ let actorIds: string[] = [];
179
+
180
+ {
181
+ using loggerContext = mcpLogger(server);
182
+ const sceneName = await getCurrentScene(editorId);
183
+ actorIds = await addGltf({
184
+ sceneName,
185
+ gltfs: gltfs.map(gltf => ({
186
+ path: gltf.path,
187
+ transform: {
188
+ position: new THREE.Vector3(...(gltf.transform?.position ?? [0, 0, 0])),
189
+ rotation: new THREE.Euler(...(gltf.transform?.rotation ?? [0, 0, 0])),
190
+ scale: new THREE.Vector3(...(gltf.transform?.scale ?? [1, 1, 1])),
191
+ },
192
+ miscInfo: {
193
+ displayName: gltf.info.displayName,
194
+ description: gltf.info.description ?? '',
195
+ },
196
+ }))
197
+ });
198
+
199
+ if (actorIds.length > 0) {
200
+ requestEditorReload(editorId);
201
+ }
202
+ }
203
+
204
+ const uuids = actorIds.join(', ');
205
+ const text = `GLTFs added successfully, placed actor UUIDs are [${uuids}]`;
206
+ return {
207
+ content: [{ type: 'text', text }]
208
+ };
209
+ }
210
+ );
211
+
212
+ server.registerTool(
213
+ 'placePrefab',
214
+ {
215
+ description: 'Place prefabs in the scene.',
216
+ inputSchema: {
217
+ editorId: EditorIdSchema,
218
+ prefabs: z.array(
219
+ z.object({
220
+ path: z.string().describe('Path to the prefab to place'),
221
+ transform: TransformSchema.optional().describe('Transform of the prefab'),
222
+ info: ActorInfoSchema.describe('Information about the prefab actor.'),
223
+ }).strict().describe('A prefab to place.')
224
+ ).describe('A list of prefabs to place. Each prefab can have a transform.'),
225
+ },
226
+ },
227
+ async ({ editorId, prefabs }) => {
228
+ let actorIds: string[] = [];
229
+
230
+ {
231
+ using loggerContext = mcpLogger(server);
232
+ const sceneName = await getCurrentScene(editorId);
233
+ actorIds = await placePrefab({
234
+ sceneName,
235
+ prefabs: prefabs.map(prefab => ({
236
+ path: prefab.path,
237
+ transform: {
238
+ position: new THREE.Vector3(...(prefab.transform?.position ?? [0, 0, 0])),
239
+ rotation: new THREE.Euler(...(prefab.transform?.rotation ?? [0, 0, 0])),
240
+ scale: new THREE.Vector3(...(prefab.transform?.scale ?? [1, 1, 1])),
241
+ },
242
+ miscInfo: {
243
+ displayName: prefab.info.displayName,
244
+ description: prefab.info.description ?? '',
245
+ },
246
+ }))
247
+ });
248
+
249
+ if (actorIds.length > 0) {
250
+ requestEditorReload(editorId);
251
+ }
252
+ }
253
+ const uuids = actorIds.join(', ');
254
+ const text = `Prefabs placed successfully, placed actor UUIDs are [${uuids}]`;
255
+ return {
256
+ content: [{ type: 'text', text }]
257
+ };
258
+ }
259
+ );
260
+
261
+
262
+ server.registerTool(
263
+ 'searchAssets',
264
+ {
265
+ description: `Search for assets in the project and engine directories.
266
+ When the user requests to place something in the scene, unless it is clearly a primitive, always try calling this first to see if any assets are fitting the request.
267
+ Before using the assets found, **always** call the \`getAssetsMetadata\` tool to get the metadata about the assets, unless the given metadata is already fetched.
268
+ Here is the list of assets types, including:
269
+ ${Object.entries(assetDescriptions).map(([type, description]) => `- ${type}: ${description}`).join('\n')}
270
+ `,
271
+ inputSchema: {
272
+ acceptedTypes: z.array(z.enum(Object.values(AssetType) as [string, ...string[]])).describe('List of types of assets to search for. An empty array means all types.'),
273
+ searchKeywords: z.array(z.string()).describe('List of keywords to search for in the assets. Wildcards are not supported, use multiple keywords to search for multiple terms. Use an empty array to search for all assets.'),
274
+ },
275
+ },
276
+ async ({ acceptedTypes, searchKeywords }) => {
277
+ using loggerContext = mcpLogger(server);
278
+
279
+ const dirs = [`${ENGINE.PROJECT_PATH_PREFIX}/assets`, `${ENGINE.ENGINE_PATH_PREFIX}/assets`];
280
+ const {assets, metadataDescription} = await searchForAssets(dirs, acceptedTypes as AssetType[], searchKeywords);
281
+
282
+ const result: CallToolResult = {content: []};
283
+ if (metadataDescription && Object.keys(metadataDescription).length > 0) {
284
+ result.content.push(
285
+ { type: 'text', text: `Here is the description of the metadata fields of the assets, per asset type:\n${JSON.stringify(metadataDescription, null, 2)}` }
286
+ );
287
+ }
288
+
289
+ result.content.push({ type: 'text', text: `Assets:\n${JSON.stringify(assets, null, 2)}` });
290
+ return result;
291
+ }
292
+ );
293
+
294
+
295
+ server.registerTool(
296
+ 'getAssetsMetadata',
297
+ {
298
+ description: 'Get metadata for the assets found by the `searchAssets` tool.',
299
+ inputSchema: {
300
+ assetPaths: z.array(z.string()).describe('List of asset paths to get metadata for. The asset paths are the keys retrieved from the result of \`searchAssets\` tool.'),
301
+ },
302
+ },
303
+ async ({ assetPaths }) => {
304
+ using loggerContext = mcpLogger(server);
305
+
306
+ try {
307
+ const assetsInfo = await populateAssets(assetPaths, { peek: false });
308
+ const metadataDescription = assetsInfo.metadataDescription;
309
+ const assets = assetsInfo.assets;
310
+
311
+ const result: CallToolResult = {content: []};
312
+ if (metadataDescription && Object.keys(metadataDescription).length > 0) {
313
+ result.content.push(
314
+ { type: 'text', text: `Here is the description of the metadata fields of the assets, per asset type:\n${JSON.stringify(metadataDescription, null, 2)}` }
315
+ );
316
+ }
317
+
318
+ result.content.push({ type: 'text', text: `Assets:\n${JSON.stringify(assets, null, 2)}` });
319
+ return result;
320
+
321
+ } catch (error) {
322
+ return {
323
+ isError: true,
324
+ content: [{ type: 'text', text: `Error getting assets metadata: ${error}` }]
325
+ };
326
+ }
327
+ }
328
+ );
329
+
330
+
331
+ server.registerTool(
332
+ 'placeJsClassActor',
333
+ {
334
+ description: 'Place a JavaScript game or engine class actor in the scene.',
335
+ inputSchema: {
336
+ editorId: EditorIdSchema,
337
+ actors: z.array(
338
+ z.object({
339
+ className: z.string().describe(`The name of the JavaScript class to place. ${classRule}`),
340
+ constructorParams: z.array(z.record(z.any())).optional()
341
+ .describe(`Optional constructor parameters to pass to the actor class.
342
+ Use the getAssetsMetadata tool to discover what parameters each actor class requires,
343
+ and the parameters should be an array that corresponds to the "jsClassConstructorParams" from the result of the getAssetsMetadata tool.
344
+ If the actor class has no constructor parameters, the array should be empty.
345
+ `),
346
+ info: ActorInfoSchema.describe('Information about the actor.'),
347
+ }).strict().describe('A JavaScript class actor to place.')
348
+ ).describe('A list of JavaScript class actors to place. Each actor can have a transform and constructor parameters.'),
349
+ },
350
+ },
351
+ async ({ editorId, actors }) => {
352
+ using loggerContext = mcpLogger(server);
353
+
354
+ const sceneName = await getCurrentScene(editorId);
355
+
356
+ const result = await placeJsClassActor({sceneName, jsClasses: actors.map(actor => ({
357
+ className: actor.className,
358
+ constructorParams: actor.constructorParams,
359
+ actorInfo: actor.info,
360
+ }))});
361
+
362
+ if (result.length > 0) {
363
+ requestEditorReload(editorId);
364
+ }
365
+
366
+ const uuids = result.join(', ');
367
+ const text = `JavaScript class actors placed successfully, placed actor UUIDs are [${uuids}]`;
368
+ return {
369
+ content: [{ type: 'text', text }]
370
+ };
371
+ }
372
+ );
373
+
374
+
375
+ server.registerTool(
376
+ 'generateTemplateCode',
377
+ {
378
+ description: 'Generate template code for a class of actor or component. When the user asks to create a new actor or component, always try calling this first to see if any template code is available, and iterate on the code it generates until it is correct.',
379
+ inputSchema: {
380
+ className: z.string().describe('The name of the class to generate template code for.'),
381
+ filePath: z.string().describe(`The path to the file to generate the template code for.
382
+ If it is a relative path, it will be relative to the 'src/' directory, so don't include the extra 'src/' in the path.
383
+ The file name should be lower camel case, such as 'myActor.ts', or 'fancyMovementComponent.ts'.`
384
+ ),
385
+ baseClassName: z.string().describe(`The name of the base class to generate the template code for. ${classRule}`),
386
+ },
387
+ },
388
+ async ({ className, filePath, baseClassName }) => {
389
+ using loggerContext = mcpLogger(server);
390
+
391
+ const fullFilePath = pathlib.isAbsolute(filePath) ? filePath : pathlib.join('src', filePath);
392
+ let success = false;
393
+ try {
394
+ success = await generateCode(className, fullFilePath, baseClassName);
395
+ }
396
+ catch (error) {
397
+ return {
398
+ isError: true,
399
+ content: [{ type: 'text', text: `Failed to generate code: ${error}` }]
400
+ };
401
+ }
402
+
403
+ if (success) {
404
+ return {
405
+ content: [
406
+ {
407
+ type: 'text',
408
+ text: `template code for class ${className} has been generated at ${fullFilePath}`
409
+ }
410
+ ]
411
+ };
412
+ }
413
+
414
+ return {
415
+ isError: true,
416
+ content: [{ type: 'text', text: `Error: Template code for class ${className} has not been generated.` }]
417
+ };
418
+ }
419
+ );
420
+
421
+
422
+ server.registerTool(
423
+ 'getSceneState',
424
+ {
425
+ description: `
426
+ Get the current state of the scene to understand the state of the current scene and its actors.
427
+ **DO NOT** try reading the scene file directly unless explicitly asked by the user.
428
+ The first time calling this tool, always call without specifiedActors and getDetailedComponentsInfo to have an overview of the scene.
429
+ And then call this tool with \`specifiedActors\`, and set \`getDetailedComponentsInfo\` to true, to get more detailed information about specific actors, including their bounding boxes info, etc.
430
+ `,
431
+ inputSchema: {
432
+ editorId: EditorIdSchema,
433
+ specifiedActors: z.array(z.string()).optional().describe('A list of actor UUIDs. If specified, actors not in this list will contain only very basic information, namely their UUID and name.'),
434
+ getDetailedComponentsInfo: z.boolean().optional().describe('If true, will contain detailed information about their properties, otherwise they will contain only their UUID and name. If specifiedActors is present, only actors in this list will have detailed information.'),
435
+ },
436
+ },
437
+ async ({editorId, specifiedActors, getDetailedComponentsInfo}) => {
438
+ using loggerContext = mcpLogger(server);
439
+
440
+ try {
441
+ const sceneName = await getCurrentScene(editorId);
442
+ const state = await getSceneState(sceneName, specifiedActors, getDetailedComponentsInfo);
443
+ function roundNumbersReplacer(key: string, value: any) {
444
+ if (typeof value === 'number') {
445
+ return Number(value.toFixed(5));
446
+ }
447
+ return value;
448
+ }
449
+ return {
450
+ content: [{ type: 'text', text: `The following is the state of the scene currently opened in the editor:\n\`\`\`json\n${JSON.stringify(state, roundNumbersReplacer)}\n\`\`\`` }]
451
+ };
452
+ }
453
+ catch (error) {
454
+ return {
455
+ isError: true,
456
+ content: [{ type: 'text', text: `Error getting scene state: ${error}` }]
457
+ };
458
+ }
459
+ }
460
+ );
461
+
462
+ server.registerTool(
463
+ 'updateActors',
464
+ {
465
+ description: 'Update the properties of existing actors in the scene.',
466
+ inputSchema: {
467
+ editorId: EditorIdSchema,
468
+ actors: z.array(
469
+ z.object({
470
+ uuid: z.string().describe('UUID of the actor to update'),
471
+ transform: TransformSchema.optional().describe('New transform of the actor'),
472
+ info: ActorInfoSchema.optional().describe('New information about the actor.'),
473
+ }).strict().describe('An actor to update with its new properties.')
474
+ ).describe('A list of actors to update with their new properties.'),
475
+ },
476
+ },
477
+ async ({editorId, actors}) => {
478
+ using loggerContext = mcpLogger(server);
479
+
480
+ const makeActorUpdateArg = (actor: typeof actors[number]) => {
481
+ const args: any = {};
482
+ args.uuid = actor.uuid;
483
+ if (actor.transform) {
484
+ args.transform = {};
485
+ if (actor.transform.position) {
486
+ args.transform.position = new THREE.Vector3(...actor.transform.position);
487
+ }
488
+ if (actor.transform.rotation) {
489
+ args.transform.rotation = new THREE.Euler(...actor.transform.rotation);
490
+ }
491
+ if (actor.transform.scale) {
492
+ args.transform.scale = new THREE.Vector3(...actor.transform.scale);
493
+ }
494
+ }
495
+ if (actor.info) {
496
+ args.actorInfo = {};
497
+ if (actor.info.displayName) {
498
+ args.actorInfo.displayName = actor.info.displayName;
499
+ }
500
+ if (actor.info.description) {
501
+ args.actorInfo.description = actor.info.description;
502
+ }
503
+ }
504
+ return args;
505
+ };
506
+
507
+ const sceneName = await getCurrentScene(editorId);
508
+ const updatedActorsNum = await updateActors({
509
+ sceneName,
510
+ actorsToUpdate: actors.map(actorToUpdate => makeActorUpdateArg(actorToUpdate))
511
+ });
512
+
513
+ if (updatedActorsNum > 0) {
514
+ requestEditorReload(editorId);
515
+ }
516
+
517
+ return {
518
+ content: [{ type: 'text', text: `Completed, ${updatedActorsNum} actors updated` }]
519
+ };
520
+ }
521
+ );
522
+
523
+ // Start receiving messages on stdin and sending messages on stdout
524
+ const transport = new StdioServerTransport();
525
+ await server.connect(transport);
526
+ server.server.sendLoggingMessage({level: 'info', data: 'starting up MCP server for Genesys'});