@waica/cli 0.4.2 → 0.6.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 +42 -37
- package/dist/editor/assets/{cssMode-BO8FrG9g.js → cssMode-DfLN2qUF.js} +1 -1
- package/dist/editor/assets/{freemarker2-C6rCz24J.js → freemarker2-BM-wWcXR.js} +1 -1
- package/dist/editor/assets/{handlebars-MZdu9Qw_.js → handlebars-C68SV5M9.js} +1 -1
- package/dist/editor/assets/{html-BrjX1TJ1.js → html-dQgLbIki.js} +1 -1
- package/dist/editor/assets/{htmlMode-cEvpMwiW.js → htmlMode-Dx91fUdd.js} +1 -1
- package/dist/editor/assets/{index-eTEBznqW.js → index-592ABIaz.js} +7154 -3186
- package/dist/editor/assets/{index-Btzc6zxr.css → index-Cy5pmBhN.css} +1 -1
- package/dist/editor/assets/{javascript-DHUgp9_L.js → javascript-BmsSq5ka.js} +1 -1
- package/dist/editor/assets/{jsonMode-D0PmIuaM.js → jsonMode-CWoPtX2M.js} +1 -1
- package/dist/editor/assets/{liquid-UM67NCct.js → liquid-DwnJlNJZ.js} +1 -1
- package/dist/editor/assets/{mdx-D3xXLqrP.js → mdx-DaLkvlR1.js} +1 -1
- package/dist/editor/assets/{python-BhLrcAva.js → python-Cs5X5zFp.js} +1 -1
- package/dist/editor/assets/{razor-DcG373mQ.js → razor-PTwWoRk8.js} +1 -1
- package/dist/editor/assets/{tsMode-ByTYaf45.js → tsMode-BqUQdgsR.js} +1 -1
- package/dist/editor/assets/{typescript-DfJlvcTN.js → typescript-BsJ7r2RJ.js} +1 -1
- package/dist/editor/assets/{xml-22dL0oCq.js → xml-DyyoPey4.js} +1 -1
- package/dist/editor/assets/{yaml-mgCu6yV6.js → yaml-Cjqtlp29.js} +1 -1
- package/dist/editor/index.html +2 -2
- package/dist/mcp/component-metadata.js +23 -0
- package/dist/mcp/introspection.js +4 -14
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/prefabs.js +7 -1
- package/dist/mcp/node_modules/@waica/archetype-platformer/dist/registry-data.js +3 -1
- package/dist/mcp/node_modules/@waica/archetype-platformer/package.json +3 -3
- package/dist/mcp/node_modules/@waica/behaviors/dist/chaser.d.ts +1 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/chaser.js +1 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/collectible.d.ts +1 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/collectible.js +1 -1
- package/dist/mcp/node_modules/@waica/behaviors/dist/hazard.d.ts +42 -1
- package/dist/mcp/node_modules/@waica/behaviors/dist/hazard.js +49 -3
- package/dist/mcp/node_modules/@waica/behaviors/dist/health.d.ts +82 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/health.js +147 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/index.d.ts +2 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/index.js +2 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/lifetime.d.ts +1 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/lifetime.js +1 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/out-of-bounds.d.ts +30 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/out-of-bounds.js +38 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/patrol.d.ts +1 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/patrol.js +1 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/platformer-motor.d.ts +1 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/platformer-motor.js +10 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/player-states.js +26 -0
- package/dist/mcp/node_modules/@waica/behaviors/dist/respawnable.d.ts +5 -13
- package/dist/mcp/node_modules/@waica/behaviors/dist/respawnable.js +5 -11
- package/dist/mcp/node_modules/@waica/behaviors/package.json +2 -2
- package/dist/mcp/node_modules/@waica/engine/README.md +85 -0
- package/dist/mcp/node_modules/@waica/engine/dist/authoring-defaults.d.ts +11 -0
- package/dist/mcp/node_modules/@waica/engine/dist/authoring-defaults.js +51 -0
- package/dist/mcp/node_modules/@waica/engine/dist/component-update-schedule.d.ts +39 -0
- package/dist/mcp/node_modules/@waica/engine/dist/component-update-schedule.js +157 -0
- package/dist/mcp/node_modules/@waica/engine/dist/component.d.ts +15 -1
- package/dist/mcp/node_modules/@waica/engine/dist/component.js +2 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/animated-sprite.d.ts +2 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/animated-sprite.js +11 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/sprite.d.ts +1 -0
- package/dist/mcp/node_modules/@waica/engine/dist/components/sprite.js +1 -0
- package/dist/mcp/node_modules/@waica/engine/dist/game.d.ts +7 -0
- package/dist/mcp/node_modules/@waica/engine/dist/game.js +79 -1
- package/dist/mcp/node_modules/@waica/engine/dist/index.d.ts +8 -1
- package/dist/mcp/node_modules/@waica/engine/dist/index.js +4 -0
- package/dist/mcp/node_modules/@waica/engine/dist/input.d.ts +10 -0
- package/dist/mcp/node_modules/@waica/engine/dist/input.js +40 -2
- package/dist/mcp/node_modules/@waica/engine/dist/runtime-bridge.d.ts +69 -0
- package/dist/mcp/node_modules/@waica/engine/dist/runtime-bridge.js +105 -0
- package/dist/mcp/node_modules/@waica/engine/dist/runtime-inspection.d.ts +71 -0
- package/dist/mcp/node_modules/@waica/engine/dist/runtime-inspection.js +279 -0
- package/dist/mcp/node_modules/@waica/engine/dist/state/state-machine.d.ts +2 -1
- package/dist/mcp/node_modules/@waica/engine/dist/state/state-machine.js +7 -1
- package/dist/mcp/node_modules/@waica/engine/package.json +3 -2
- package/dist/mcp/package-resolver.js +10 -2
- package/dist/mcp/project-component-loader.js +553 -0
- package/dist/mcp/project-component-runner.js +237 -0
- package/dist/mcp/runtime-browser.js +336 -0
- package/dist/mcp/runtime-dev-server.js +273 -0
- package/dist/mcp/runtime-preflight.js +249 -0
- package/dist/mcp/runtime-service.js +8 -0
- package/dist/mcp/runtime-session-manager.js +265 -0
- package/dist/mcp/scaffolds.js +126 -6
- package/dist/mcp/server.js +330 -9
- package/dist/mcp/template/src/characters/player.character.json +29 -2
- package/dist/mcp/template/src/characters/slime.character.json +6 -0
- package/dist/mcp/template/src/main.ts +6 -1
- package/dist/mcp/validation.js +240 -24
- package/package.json +3 -2
package/dist/mcp/server.js
CHANGED
|
@@ -6,7 +6,10 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextpro
|
|
|
6
6
|
import { createProject } from './create-project.js';
|
|
7
7
|
import { describeArchetype, listComponents, projectSummary, } from './introspection.js';
|
|
8
8
|
import { ABSOLUTE_PATH_MESSAGE, WaicaToolError, assertAbsoluteProjectPath, requireWaicaProject, } from './project-path.js';
|
|
9
|
-
import { scaffoldComponent, scaffoldRole, scaffoldState, scaffoldUi, } from './scaffolds.js';
|
|
9
|
+
import { scaffoldComponent, scaffoldPrefab, scaffoldRole, scaffoldState, scaffoldUi, } from './scaffolds.js';
|
|
10
|
+
import { RuntimeToolError, } from './runtime-service.js';
|
|
11
|
+
import { createDefaultRuntimeSessionManager } from './runtime-session-manager.js';
|
|
12
|
+
import { ProjectComponentLoader } from './project-component-loader.js';
|
|
10
13
|
import { validateProject } from './validation.js';
|
|
11
14
|
const PROJECT_PATH = {
|
|
12
15
|
type: 'string',
|
|
@@ -69,6 +72,29 @@ export const TOOLS = [
|
|
|
69
72
|
inputSchema: schema({ name: { type: 'string', minLength: 1 } }, ['name']),
|
|
70
73
|
annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
71
74
|
},
|
|
75
|
+
{
|
|
76
|
+
name: 'scaffold_prefab',
|
|
77
|
+
description: 'Create the editor-compatible starter prefab for a character, object or tile.',
|
|
78
|
+
inputSchema: schema({
|
|
79
|
+
name: { type: 'string', minLength: 1, description: 'Prefab file name, without the type suffix.' },
|
|
80
|
+
type: {
|
|
81
|
+
type: 'string',
|
|
82
|
+
enum: ['character', 'object', 'tile'],
|
|
83
|
+
description: 'Prefab category; it decides the directory and the file suffix.',
|
|
84
|
+
},
|
|
85
|
+
role: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
minLength: 1,
|
|
88
|
+
description: 'Character role from the active archetype (characters only); defaults to player.',
|
|
89
|
+
},
|
|
90
|
+
identity: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
enum: ['player', 'enemy', 'npc', 'custom'],
|
|
93
|
+
description: 'What the character is to the game (characters only); adds its starter components.',
|
|
94
|
+
},
|
|
95
|
+
}, ['name', 'type']),
|
|
96
|
+
annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
97
|
+
},
|
|
72
98
|
{
|
|
73
99
|
name: 'scaffold_role',
|
|
74
100
|
description: 'Create the editor-compatible starter for a custom character role.',
|
|
@@ -95,7 +121,92 @@ export const TOOLS = [
|
|
|
95
121
|
inputSchema: schema({ name: { type: 'string', minLength: 1 } }, ['name']),
|
|
96
122
|
annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
97
123
|
},
|
|
124
|
+
{
|
|
125
|
+
name: 'start_project',
|
|
126
|
+
description: 'Start or reuse a browser-backed Run Session for a trusted Waica Project.',
|
|
127
|
+
inputSchema: schema({
|
|
128
|
+
browser_executable_path: { type: 'string', minLength: 1 },
|
|
129
|
+
headless: { type: 'boolean', default: true },
|
|
130
|
+
viewport: {
|
|
131
|
+
type: 'object',
|
|
132
|
+
properties: {
|
|
133
|
+
width: { type: 'integer', minimum: 1 },
|
|
134
|
+
height: { type: 'integer', minimum: 1 },
|
|
135
|
+
},
|
|
136
|
+
required: ['width', 'height'],
|
|
137
|
+
additionalProperties: false,
|
|
138
|
+
},
|
|
139
|
+
timeout_ms: { type: 'integer', minimum: 1_000, maximum: 120_000, default: 30_000 },
|
|
140
|
+
}),
|
|
141
|
+
annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'stop_project',
|
|
145
|
+
description: 'Stop a Project Run Session and all browser and process resources it owns.',
|
|
146
|
+
inputSchema: schema({}),
|
|
147
|
+
annotations: { destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'inspect_runtime',
|
|
151
|
+
description: 'Read a filtered Runtime Snapshot from a running Project.',
|
|
152
|
+
inputSchema: schema({
|
|
153
|
+
entity_ids: { type: 'array', items: { type: 'string' } },
|
|
154
|
+
entity_names: { type: 'array', items: { type: 'string' } },
|
|
155
|
+
component_types: { type: 'array', items: { type: 'string' } },
|
|
156
|
+
}),
|
|
157
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'control_runtime',
|
|
161
|
+
description: 'Inject a semantic action or change deterministic frame control for a Run Session.',
|
|
162
|
+
inputSchema: {
|
|
163
|
+
type: 'object',
|
|
164
|
+
properties: {
|
|
165
|
+
project_path: PROJECT_PATH,
|
|
166
|
+
operation: {
|
|
167
|
+
type: 'string',
|
|
168
|
+
enum: ['press', 'hold', 'release', 'pause', 'resume', 'step'],
|
|
169
|
+
},
|
|
170
|
+
action: { type: 'string', minLength: 1 },
|
|
171
|
+
dt: { type: 'number', exclusiveMinimum: 0, maximum: 0.1 },
|
|
172
|
+
frames: { type: 'integer', minimum: 1, maximum: 600 },
|
|
173
|
+
},
|
|
174
|
+
required: ['project_path', 'operation'],
|
|
175
|
+
additionalProperties: false,
|
|
176
|
+
oneOf: [
|
|
177
|
+
{
|
|
178
|
+
properties: { operation: { enum: ['press', 'hold', 'release'] } },
|
|
179
|
+
required: ['action'],
|
|
180
|
+
not: { anyOf: [{ required: ['dt'] }, { required: ['frames'] }] },
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
properties: { operation: { enum: ['pause', 'resume'] } },
|
|
184
|
+
not: {
|
|
185
|
+
anyOf: [{ required: ['action'] }, { required: ['dt'] }, { required: ['frames'] }],
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
properties: { operation: { const: 'step' } },
|
|
190
|
+
not: { anyOf: [{ required: ['action'] }] },
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
annotations: { destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: 'capture_screenshot',
|
|
198
|
+
description: 'Capture the composited Waica Game surface from a running Project.',
|
|
199
|
+
inputSchema: schema({}),
|
|
200
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
201
|
+
},
|
|
98
202
|
];
|
|
203
|
+
const RUNTIME_TOOL_NAMES = new Set([
|
|
204
|
+
'start_project',
|
|
205
|
+
'stop_project',
|
|
206
|
+
'inspect_runtime',
|
|
207
|
+
'control_runtime',
|
|
208
|
+
'capture_screenshot',
|
|
209
|
+
]);
|
|
99
210
|
function requiredString(args, name, projectPath = '') {
|
|
100
211
|
const value = args[name];
|
|
101
212
|
if (typeof value === 'string' && value.length > 0)
|
|
@@ -106,11 +217,131 @@ function requiredString(args, name, projectPath = '') {
|
|
|
106
217
|
projectPath,
|
|
107
218
|
});
|
|
108
219
|
}
|
|
109
|
-
|
|
220
|
+
function invalidRuntimeInput(name, projectPath, message) {
|
|
221
|
+
throw new RuntimeToolError({
|
|
222
|
+
code: 'runtime-operation-failed',
|
|
223
|
+
stage: name === 'control_runtime' ? 'control' : 'project',
|
|
224
|
+
message,
|
|
225
|
+
projectPath,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function assertOnlyRuntimeFields(name, args, allowed, projectPath) {
|
|
229
|
+
const extras = Object.keys(args).filter((key) => !allowed.includes(key));
|
|
230
|
+
if (extras.length > 0) {
|
|
231
|
+
invalidRuntimeInput(name, projectPath, `Unexpected properties: ${extras.sort().join(', ')}.`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function assertStringArray(name, args, field, projectPath) {
|
|
235
|
+
const value = args[field];
|
|
236
|
+
if (value !== undefined && (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string'))) {
|
|
237
|
+
invalidRuntimeInput(name, projectPath, `${field} must be an array of strings.`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function validateRuntimeArguments(name, args, projectPath) {
|
|
241
|
+
switch (name) {
|
|
242
|
+
case 'start_project': {
|
|
243
|
+
assertOnlyRuntimeFields(name, args, ['project_path', 'browser_executable_path', 'headless', 'viewport', 'timeout_ms'], projectPath);
|
|
244
|
+
if (args.browser_executable_path !== undefined &&
|
|
245
|
+
(typeof args.browser_executable_path !== 'string' || args.browser_executable_path.length === 0)) {
|
|
246
|
+
invalidRuntimeInput(name, projectPath, 'browser_executable_path must be a nonempty string.');
|
|
247
|
+
}
|
|
248
|
+
if (args.headless !== undefined && typeof args.headless !== 'boolean') {
|
|
249
|
+
invalidRuntimeInput(name, projectPath, 'headless must be a boolean.');
|
|
250
|
+
}
|
|
251
|
+
if (args.timeout_ms !== undefined) {
|
|
252
|
+
const timeout = args.timeout_ms;
|
|
253
|
+
if (typeof timeout !== 'number' || !Number.isInteger(timeout) || timeout < 1_000 || timeout > 120_000) {
|
|
254
|
+
invalidRuntimeInput(name, projectPath, 'timeout_ms must be an integer from 1,000 through 120,000.');
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (args.viewport !== undefined) {
|
|
258
|
+
if (!args.viewport || typeof args.viewport !== 'object' || Array.isArray(args.viewport)) {
|
|
259
|
+
invalidRuntimeInput(name, projectPath, 'viewport must contain width and height.');
|
|
260
|
+
}
|
|
261
|
+
const viewport = args.viewport;
|
|
262
|
+
const keys = Object.keys(viewport);
|
|
263
|
+
if (keys.some((key) => key !== 'width' && key !== 'height') ||
|
|
264
|
+
keys.length !== 2 ||
|
|
265
|
+
typeof viewport.width !== 'number' ||
|
|
266
|
+
typeof viewport.height !== 'number' ||
|
|
267
|
+
!Number.isInteger(viewport.width) ||
|
|
268
|
+
!Number.isInteger(viewport.height) ||
|
|
269
|
+
viewport.width <= 0 ||
|
|
270
|
+
viewport.height <= 0 ||
|
|
271
|
+
viewport.width * viewport.height > 1_000_000) {
|
|
272
|
+
invalidRuntimeInput(name, projectPath, 'viewport width and height must be positive integers totaling at most 1,000,000 pixels.');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
case 'stop_project':
|
|
278
|
+
case 'capture_screenshot':
|
|
279
|
+
assertOnlyRuntimeFields(name, args, ['project_path'], projectPath);
|
|
280
|
+
return;
|
|
281
|
+
case 'inspect_runtime':
|
|
282
|
+
assertOnlyRuntimeFields(name, args, ['project_path', 'entity_ids', 'entity_names', 'component_types'], projectPath);
|
|
283
|
+
assertStringArray(name, args, 'entity_ids', projectPath);
|
|
284
|
+
assertStringArray(name, args, 'entity_names', projectPath);
|
|
285
|
+
assertStringArray(name, args, 'component_types', projectPath);
|
|
286
|
+
return;
|
|
287
|
+
case 'control_runtime': {
|
|
288
|
+
assertOnlyRuntimeFields(name, args, ['project_path', 'operation', 'action', 'dt', 'frames'], projectPath);
|
|
289
|
+
const operation = args.operation;
|
|
290
|
+
if (!['press', 'hold', 'release', 'pause', 'resume', 'step'].includes(String(operation))) {
|
|
291
|
+
invalidRuntimeInput(name, projectPath, 'operation is not a supported runtime control operation.');
|
|
292
|
+
}
|
|
293
|
+
if (operation === 'press' || operation === 'hold' || operation === 'release') {
|
|
294
|
+
if (typeof args.action !== 'string' || args.action.length === 0) {
|
|
295
|
+
invalidRuntimeInput(name, projectPath, `${operation} requires a nonempty action.`);
|
|
296
|
+
}
|
|
297
|
+
if (args.dt !== undefined || args.frames !== undefined) {
|
|
298
|
+
invalidRuntimeInput(name, projectPath, `${operation} does not accept dt or frames.`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
else if (operation === 'pause' || operation === 'resume') {
|
|
302
|
+
if (args.action !== undefined || args.dt !== undefined || args.frames !== undefined) {
|
|
303
|
+
invalidRuntimeInput(name, projectPath, `${operation} accepts no additional fields.`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
if (args.action !== undefined)
|
|
308
|
+
invalidRuntimeInput(name, projectPath, 'step does not accept action.');
|
|
309
|
+
if (args.dt !== undefined &&
|
|
310
|
+
(typeof args.dt !== 'number' || !Number.isFinite(args.dt) || args.dt <= 0 || args.dt > 0.1)) {
|
|
311
|
+
invalidRuntimeInput(name, projectPath, 'dt must be finite and greater than 0 and at most 0.1.');
|
|
312
|
+
}
|
|
313
|
+
if (args.frames !== undefined &&
|
|
314
|
+
(typeof args.frames !== 'number' || !Number.isInteger(args.frames) || args.frames < 1 || args.frames > 600)) {
|
|
315
|
+
invalidRuntimeInput(name, projectPath, 'frames must be an integer from 1 through 600.');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async function execute(name, args, runtime, signal, componentLoader) {
|
|
323
|
+
const isRuntimeTool = RUNTIME_TOOL_NAMES.has(name);
|
|
324
|
+
const rawProjectPath = args.project_path;
|
|
325
|
+
if (isRuntimeTool && (typeof rawProjectPath !== 'string' || rawProjectPath.length === 0)) {
|
|
326
|
+
invalidRuntimeInput(name, '', 'project_path must be a nonempty absolute path.');
|
|
327
|
+
}
|
|
110
328
|
const projectPath = requiredString(args, 'project_path');
|
|
111
329
|
// Keep this at the dispatch boundary so every tool has byte-identical
|
|
112
330
|
// stdio-cwd semantics, including create_project.
|
|
113
|
-
|
|
331
|
+
if (isRuntimeTool) {
|
|
332
|
+
if (!path.isAbsolute(projectPath)) {
|
|
333
|
+
throw new RuntimeToolError({
|
|
334
|
+
code: 'runtime-prerequisite-missing',
|
|
335
|
+
stage: 'project',
|
|
336
|
+
message: ABSOLUTE_PATH_MESSAGE,
|
|
337
|
+
projectPath,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
validateRuntimeArguments(name, args, projectPath);
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
assertAbsoluteProjectPath(projectPath);
|
|
344
|
+
}
|
|
114
345
|
switch (name) {
|
|
115
346
|
case 'create_project': {
|
|
116
347
|
const start = args.start === undefined ? 'demo' : requiredString(args, 'start', projectPath);
|
|
@@ -130,7 +361,7 @@ async function execute(name, args) {
|
|
|
130
361
|
case 'project_summary':
|
|
131
362
|
return projectSummary(projectPath);
|
|
132
363
|
case 'validate_project':
|
|
133
|
-
return validateProject(projectPath);
|
|
364
|
+
return validateProject(projectPath, { signal, componentLoader });
|
|
134
365
|
case 'scaffold_component': {
|
|
135
366
|
const check = await requireWaicaProject(projectPath);
|
|
136
367
|
return {
|
|
@@ -140,6 +371,15 @@ async function execute(name, args) {
|
|
|
140
371
|
warnings: [],
|
|
141
372
|
};
|
|
142
373
|
}
|
|
374
|
+
case 'scaffold_prefab': {
|
|
375
|
+
const check = await requireWaicaProject(projectPath);
|
|
376
|
+
return {
|
|
377
|
+
...(await scaffoldPrefab(projectPath, requiredString(args, 'name', projectPath), requiredString(args, 'type', projectPath), args.role === undefined ? undefined : requiredString(args, 'role', projectPath), args.identity === undefined ? undefined : requiredString(args, 'identity', projectPath))),
|
|
378
|
+
notes: check.notes,
|
|
379
|
+
provenance: [],
|
|
380
|
+
warnings: [],
|
|
381
|
+
};
|
|
382
|
+
}
|
|
143
383
|
case 'scaffold_role': {
|
|
144
384
|
const check = await requireWaicaProject(projectPath);
|
|
145
385
|
return {
|
|
@@ -167,6 +407,39 @@ async function execute(name, args) {
|
|
|
167
407
|
warnings: [],
|
|
168
408
|
};
|
|
169
409
|
}
|
|
410
|
+
case 'start_project':
|
|
411
|
+
return runtime.start({
|
|
412
|
+
projectPath,
|
|
413
|
+
...(typeof args.browser_executable_path === 'string'
|
|
414
|
+
? { browserExecutablePath: args.browser_executable_path }
|
|
415
|
+
: {}),
|
|
416
|
+
...(typeof args.headless === 'boolean' ? { headless: args.headless } : {}),
|
|
417
|
+
...(args.viewport && typeof args.viewport === 'object'
|
|
418
|
+
? { viewport: args.viewport }
|
|
419
|
+
: {}),
|
|
420
|
+
...(typeof args.timeout_ms === 'number' ? { timeoutMs: args.timeout_ms } : {}),
|
|
421
|
+
});
|
|
422
|
+
case 'stop_project':
|
|
423
|
+
return runtime.stop(projectPath);
|
|
424
|
+
case 'inspect_runtime':
|
|
425
|
+
return runtime.inspect({
|
|
426
|
+
projectPath,
|
|
427
|
+
...(Array.isArray(args.entity_ids) ? { entityIds: args.entity_ids } : {}),
|
|
428
|
+
...(Array.isArray(args.entity_names) ? { entityNames: args.entity_names } : {}),
|
|
429
|
+
...(Array.isArray(args.component_types)
|
|
430
|
+
? { componentTypes: args.component_types }
|
|
431
|
+
: {}),
|
|
432
|
+
});
|
|
433
|
+
case 'control_runtime':
|
|
434
|
+
return runtime.control({
|
|
435
|
+
projectPath,
|
|
436
|
+
operation: requiredString(args, 'operation', projectPath),
|
|
437
|
+
...(typeof args.action === 'string' ? { action: args.action } : {}),
|
|
438
|
+
...(typeof args.dt === 'number' ? { dt: args.dt } : {}),
|
|
439
|
+
...(typeof args.frames === 'number' ? { frames: args.frames } : {}),
|
|
440
|
+
});
|
|
441
|
+
case 'capture_screenshot':
|
|
442
|
+
return runtime.captureScreenshot(projectPath);
|
|
170
443
|
default:
|
|
171
444
|
throw new WaicaToolError({
|
|
172
445
|
code: 'unknown-tool',
|
|
@@ -186,11 +459,39 @@ function result(payload, isError = false) {
|
|
|
186
459
|
...(isError ? { isError: true } : {}),
|
|
187
460
|
};
|
|
188
461
|
}
|
|
189
|
-
function
|
|
462
|
+
function screenshotResult(screenshot) {
|
|
463
|
+
const metadata = jsonSafe(screenshot.metadata);
|
|
464
|
+
return {
|
|
465
|
+
content: [
|
|
466
|
+
{ type: 'text', text: JSON.stringify(metadata, null, 2) },
|
|
467
|
+
{ type: 'image', mimeType: 'image/png', data: screenshot.data },
|
|
468
|
+
],
|
|
469
|
+
structuredContent: metadata,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
function errorResult(error, args, toolName) {
|
|
190
473
|
const projectPath = typeof args.project_path === 'string' ? args.project_path : '';
|
|
474
|
+
if (error instanceof RuntimeToolError) {
|
|
475
|
+
return result({ error: error.body }, true);
|
|
476
|
+
}
|
|
191
477
|
if (error instanceof WaicaToolError) {
|
|
192
478
|
return result({ error: error.body, provenance: [] }, true);
|
|
193
479
|
}
|
|
480
|
+
if (RUNTIME_TOOL_NAMES.has(toolName)) {
|
|
481
|
+
const stage = toolName === 'control_runtime'
|
|
482
|
+
? 'control'
|
|
483
|
+
: toolName === 'stop_project'
|
|
484
|
+
? 'cleanup'
|
|
485
|
+
: 'game';
|
|
486
|
+
return result({
|
|
487
|
+
error: {
|
|
488
|
+
code: 'runtime-operation-failed',
|
|
489
|
+
stage,
|
|
490
|
+
message: error instanceof Error ? error.message : String(error),
|
|
491
|
+
projectPath,
|
|
492
|
+
},
|
|
493
|
+
}, true);
|
|
494
|
+
}
|
|
194
495
|
return result({
|
|
195
496
|
error: {
|
|
196
497
|
code: 'tool-error',
|
|
@@ -224,7 +525,9 @@ function shippedVersion() {
|
|
|
224
525
|
directory = parent;
|
|
225
526
|
}
|
|
226
527
|
}
|
|
227
|
-
export function createWaicaMcpServer() {
|
|
528
|
+
export function createWaicaMcpServer(options = {}) {
|
|
529
|
+
const runtime = options.runtime ?? createDefaultRuntimeSessionManager();
|
|
530
|
+
const componentLoader = options.componentLoader ?? new ProjectComponentLoader();
|
|
228
531
|
const server = new Server(
|
|
229
532
|
// The name stays fixed: it identifies the server to the host, not the
|
|
230
533
|
// package that happens to carry it.
|
|
@@ -233,15 +536,33 @@ export function createWaicaMcpServer() {
|
|
|
233
536
|
instructions: 'Operate on user Waica projects through absolute project_path values. Keep editing JSON and TypeScript with the host file tools.',
|
|
234
537
|
});
|
|
235
538
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
236
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
539
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
237
540
|
const args = (request.params.arguments ?? {});
|
|
238
541
|
try {
|
|
239
|
-
|
|
542
|
+
const executed = await execute(request.params.name, args, runtime, extra.signal, componentLoader);
|
|
543
|
+
return request.params.name === 'capture_screenshot'
|
|
544
|
+
? screenshotResult(executed)
|
|
545
|
+
: result(executed);
|
|
240
546
|
}
|
|
241
547
|
catch (error) {
|
|
242
|
-
|
|
548
|
+
if (extra.signal.aborted)
|
|
549
|
+
throw (extra.signal.reason ?? error);
|
|
550
|
+
return errorResult(error, args, request.params.name);
|
|
243
551
|
}
|
|
244
552
|
});
|
|
553
|
+
let cleanup;
|
|
554
|
+
const cleanupResources = () => {
|
|
555
|
+
cleanup ??= Promise.all([runtime.close(), componentLoader.close()]).then(() => undefined);
|
|
556
|
+
return cleanup;
|
|
557
|
+
};
|
|
558
|
+
server.onclose = () => {
|
|
559
|
+
void cleanupResources();
|
|
560
|
+
};
|
|
561
|
+
const closeProtocol = server.close.bind(server);
|
|
562
|
+
server.close = async () => {
|
|
563
|
+
await cleanupResources();
|
|
564
|
+
await closeProtocol();
|
|
565
|
+
};
|
|
245
566
|
return server;
|
|
246
567
|
}
|
|
247
568
|
export { ABSOLUTE_PATH_MESSAGE };
|
|
@@ -112,6 +112,23 @@
|
|
|
112
112
|
"to": "idle"
|
|
113
113
|
}
|
|
114
114
|
]
|
|
115
|
+
},
|
|
116
|
+
"dead": {
|
|
117
|
+
"clip": "idle",
|
|
118
|
+
"transitions": [
|
|
119
|
+
{
|
|
120
|
+
"on": "timer:0.8",
|
|
121
|
+
"to": "idle"
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
"*": {
|
|
126
|
+
"transitions": [
|
|
127
|
+
{
|
|
128
|
+
"on": "signal:death",
|
|
129
|
+
"to": "dead"
|
|
130
|
+
}
|
|
131
|
+
]
|
|
115
132
|
}
|
|
116
133
|
}
|
|
117
134
|
}
|
|
@@ -124,9 +141,19 @@
|
|
|
124
141
|
}
|
|
125
142
|
},
|
|
126
143
|
{
|
|
127
|
-
"type": "Respawnable"
|
|
144
|
+
"type": "Respawnable"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"type": "Health",
|
|
148
|
+
"props": {
|
|
149
|
+
"max": 3,
|
|
150
|
+
"invulnerability": 1
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"type": "OutOfBounds",
|
|
128
155
|
"props": {
|
|
129
|
-
"
|
|
156
|
+
"minY": -12
|
|
130
157
|
}
|
|
131
158
|
}
|
|
132
159
|
]
|
|
@@ -14,7 +14,12 @@ import settings from './game.json'
|
|
|
14
14
|
|
|
15
15
|
// Your components, roles and state code extend the installed archetype
|
|
16
16
|
// baseline when main() imports them.
|
|
17
|
-
const projectCode = import.meta.glob([
|
|
17
|
+
const projectCode = import.meta.glob([
|
|
18
|
+
'./components/*.ts',
|
|
19
|
+
'./roles/*.ts',
|
|
20
|
+
'./states/*.ts',
|
|
21
|
+
'!**/*.test.ts',
|
|
22
|
+
])
|
|
18
23
|
|
|
19
24
|
// Your prefabs ARE these files (saved by the editor, or hand-edited — they're
|
|
20
25
|
// just JSON). Nothing else: delete one and the game stops knowing about it.
|