@gripforgeai/mcp 0.1.15 → 0.1.16

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.
@@ -47,7 +47,7 @@ export function registerAssetProductionTools(register, options, schema = z) {
47
47
  }));
48
48
  register('gripforge_asset_search', {
49
49
  title: 'Search workspace and Community assets',
50
- description: 'Search actual asset models, materials and animations, independently of Game Kit modules. Returns provenance, import requirements and Library ids. Results are ranked best first by fitness for a GripForge game (score/100, grade A–D, reasons: triangle budget for the target, rigged and clean rig, clips, empty-handed characters, attachable weapons, classified, file size, adoption); `best` is the top pick across sources. Prefer grade A/B; read the reasons before choosing a C/D. Search both sources before manufacturing missing roles. An empty kit search does not mean there are no assets. Search is free; never substitutes proxy geometry for finished models.',
50
+ description: 'Search actual asset models, materials and animations, independently of Game Kit modules. Returns provenance, import requirements and Library ids. Results are ranked best first by fitness for a GripForge game (score/100, grade A–D, reasons: triangle budget for the target, rigged and clean rig, clips, empty-handed characters, attachable weapons, classified, file size, adoption); `best` is the top pick across sources. Prefer grade A/B; read the reasons before choosing a C/D. Search both sources before manufacturing missing roles. An empty kit search does not mean there are no assets. Search is free; never substitutes proxy geometry for finished models. In a Game Kit project, use it to fill what gripforge_game_audit reports missing (a creature slot gets ranked proposals from gripforge_gamekit_creatures; a character without clips is animated, not left idle).',
51
51
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
52
52
  inputSchema: {
53
53
  q: schema.string().max(200).describe('Visual role or style, e.g. sci-fi turret, rock, soldier.'),
@@ -13,7 +13,7 @@ export function registerGameCreationTools(register, options, schema = z) {
13
13
  };
14
14
  register(GAME_CREATION_TOOL_NAMES[0], {
15
15
  title: 'Prepare and resume a game',
16
- description: 'The website and CLI share this saved 3D game creation workflow. Ask which engine (Unity, Godot, Unreal, Three.js) if unspecified; do not choose silently. create saves a brief without spending. quote(operation=plan) estimates AI preparation, accept(quote_token) starts its durable job ONLY after user approval of the shown price. get returns a persistent operation log, slots, studio_url and the same project to resume. Planning can select an appropriate preset OR compose real engine-compatible modules. A missing preset never means the genre needs a native engine. composition.modules pins all module versions; composition.tasks lists the custom logic still to implement. Assembly is a foundation, not a completed custom game: use the coding agent to finish those tasks on the selected engine (including web/Three.js). quote refine with message updates the same plan after approval, preserves selected assets and reviewed work, and persists the conversation. use_reference with slot_id and an owned image asset_id supplies an uploaded concept for visual review. assets searches compatible workspace/community items ranked by the brief and role; select chooses one or sets mode=generate with a prompt. For generation: quote concept, obtain approval, accept, poll get, show the concept, approve_concept only after visual user review; quote generate, obtain approval, accept, poll, show the model in its studio, approve_asset only after visual review. Never infer visual approval from technical success. quote build then accept acquires the selected community assets and binds them to a Game Kit prototype, without generating extra assets. With explicit allow_incomplete=true on the build quote, unreviewed models are excluded and missing slots use module defaults where available or remain empty. Show missing_assets before approval; never imply that an incomplete prototype is finished. Native engine projects still need CLI delivery/import; a web project has a play_url. Revision required for edits. Cancel/retry preserve completed steps. 2D unsupported. No automatic spending, invented asset IDs, or unrelated template substitutions.',
16
+ description: 'The website and CLI share this saved 3D game creation workflow. Ask which engine (Unity, Godot, Unreal, Three.js) if unspecified; do not choose silently. create saves a brief without spending. quote(operation=plan) estimates AI preparation, accept(quote_token) starts its durable job ONLY after user approval of the shown price. get returns a persistent operation log, slots, studio_url and the same project to resume. Planning can select an appropriate preset OR compose real engine-compatible modules. A missing preset never means the genre needs a native engine. composition.modules pins all module versions; composition.tasks lists the custom logic still to implement. Assembly is a foundation, not a completed custom game: use the coding agent to finish those tasks on the selected engine (including web/Three.js). After a build, get also returns creatures (the models bound so that no unit is a capsule) and audit { verdict: complete | playable_with_defects | incomplete, headline, fixed (the free fixes applied: models, standard clips, control scheme), defects [{ severity, message, fix { tool, args, credits? } }], next }: show the user the verdict and what is left, work through next (gripforge_game_audit reads it again), and never present a verdict other than complete as a finished game. quote refine with message updates the same plan after approval, preserves selected assets and reviewed work, and persists the conversation. use_reference with slot_id and an owned image asset_id supplies an uploaded concept for visual review. assets searches compatible workspace/community items ranked by the brief and role; select chooses one or sets mode=generate with a prompt. For generation: quote concept, obtain approval, accept, poll get, show the concept, approve_concept only after visual user review; quote generate, obtain approval, accept, poll, show the model in its studio, approve_asset only after visual review. Never infer visual approval from technical success. quote build then accept acquires the selected community assets and binds them to a Game Kit prototype, without generating extra assets. With explicit allow_incomplete=true on the build quote, unreviewed models are excluded and missing slots use module defaults where available or remain empty. Show missing_assets before approval; never imply that an incomplete prototype is finished. Native engine projects still need CLI delivery/import; a web project has a play_url. Revision required for edits. Cancel/retry preserve completed steps. 2D unsupported. No automatic spending, invented asset IDs, or unrelated template substitutions.',
17
17
  inputSchema: shape, annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
18
18
  }, async (args, extra) => {
19
19
  const parsed = schema.object(shape).strict().safeParse(args);
@@ -12,16 +12,44 @@ export const GAMEKIT_TOOL_NAMES = [
12
12
  'gripforge_gamekit_deliver',
13
13
  'gripforge_game_capabilities',
14
14
  'gripforge_game_project',
15
+ 'gripforge_gamekit_creatures',
15
16
  'gripforge_game_engine',
16
17
  'gripforge_game_content',
17
18
  'gripforge_game_play_url',
18
19
  'gripforge_game_test',
20
+ 'gripforge_game_audit',
19
21
  'gripforge_game_web_export',
20
22
  'gripforge_moba_roster',
21
23
  'gripforge_ability_vfx',
22
24
  'gripforge_moba_map',
23
25
  'gripforge_terrain_map_use',
24
26
  ];
27
+ /**
28
+ * Where each tool stands in the making of a game, appended to its description: when to call it, and the audit it
29
+ * leads to. One table, so the journey reads the same from every tool — and one place to change it.
30
+ */
31
+ export const TOOL_JOURNEY = {
32
+ gripforge_gamekit_search: 'Journey — first: find the preset of the genre. Its `done` says what a finished game of that genre has beyond the request, and gripforge_game_audit checks it at the end.',
33
+ gripforge_gamekit_installed: 'Journey — first on an existing project; gripforge_game_audit then says what stands between it and a finished game.',
34
+ gripforge_gamekit_install: 'Journey — while composing. A kit installed and left empty is not done (audio.core without a sound, ui.endscreen without an end condition): gripforge_game_audit names them.',
35
+ gripforge_gamekit_configure: 'Journey — while composing, and to fix what gripforge_game_audit reports (its defects carry the exact config to write).',
36
+ gripforge_game_capabilities: 'Journey — while composing; gripforge_game_audit includes this report, with the creatures, the controls, the playtest and the definition of done of the genre.',
37
+ gripforge_game_project: 'Journey — create, then bind a model to every creature slot (get carries the creatures report) and set the controls: action=controls applies the control scheme of the gameplay, a proposal to adapt. Before telling the user the game is done: gripforge_game_audit.',
38
+ gripforge_gamekit_creatures: 'Journey — right after creating or binding: no unit stays a capsule, every model gets its movement and action clips. gripforge_game_audit folds this report with the rest and applies the free steps with fix=true.',
39
+ gripforge_game_engine: 'Journey — the build order of the content; its last step is gripforge_game_audit, not "it loads".',
40
+ gripforge_game_content: 'Journey — the content of the game. Once written, gripforge_game_audit says what a finished game of the genre still lacks (a quest, a way to win and to lose, dialogues…).',
41
+ gripforge_game_test: 'Journey — after each change. A playtest that passes is not a finished game: gripforge_game_audit (playtest="run" runs this test) gives the verdict and everything the tester cannot see.',
42
+ gripforge_game_play_url: 'Journey — to look at the running game. Share it as finished only when gripforge_game_audit says complete; otherwise say what is left.',
43
+ gripforge_game_web_export: 'Journey — last. Run gripforge_game_audit first: export a game whose verdict is complete, or tell the user what is left.',
44
+ gripforge_gamekit_deliver: 'Journey — last. Run gripforge_game_audit first: deliver a game whose verdict is complete, or tell the user what is left.',
45
+ gripforge_moba_roster: 'Journey — a MOBA needs distinct heroes; then gripforge_game_audit for the minions, towers, jungle monsters and the rest of a finished MOBA.',
46
+ gripforge_ability_vfx: 'Journey — after the roster; gripforge_game_audit lists it among what a finished MOBA has.',
47
+ };
48
+ /** The tool's place in the journey, then its description (whose closing credit rule stays last). */
49
+ function withJourney(name, description) {
50
+ const journey = TOOL_JOURNEY[name];
51
+ return journey ? `${journey} ${description}` : description;
52
+ }
25
53
  const enc = (v) => encodeURIComponent(String(v));
26
54
  const HOSTED_DELIVERY_NOTE = 'This hosted endpoint cannot write into your project: write bundle.files following plan.actions in order, or use npx @gripforgeai/mcp with gripforge_gamekit_deliver_local { project_dir }, or the editor bridge.';
27
55
  export function registerGameKitTools(register, options, schema = z) {
@@ -43,7 +71,7 @@ export function registerGameKitTools(register, options, schema = z) {
43
71
  .record(schema.string(), schema.unknown())
44
72
  .optional()
45
73
  .describe('Kit config values, validated against the kit configSchema returned by gripforge_gamekit_get. Merged over the current config.');
46
- async function api(path, args, method, signal, query) {
74
+ async function api(path, args, method, signal, query, timeoutMs = 120_000) {
47
75
  const key = options.getApiKey();
48
76
  if (!key)
49
77
  return { isError: true, content: [{ type: 'text', text: 'GripForge API key required.' }] };
@@ -66,7 +94,7 @@ export function registerGameKitTools(register, options, schema = z) {
66
94
  ...(typeof workspace_id === 'string' ? { 'x-workspace-id': workspace_id } : {}),
67
95
  },
68
96
  ...(method === 'GET' || method === 'DELETE' ? {} : { body: JSON.stringify(body) }),
69
- signal: AbortSignal.any([AbortSignal.timeout(120_000), ...(signal ? [signal] : [])]),
97
+ signal: AbortSignal.any([AbortSignal.timeout(timeoutMs), ...(signal ? [signal] : [])]),
70
98
  });
71
99
  const data = (await res.json());
72
100
  return {
@@ -82,7 +110,7 @@ export function registerGameKitTools(register, options, schema = z) {
82
110
  function tool(name, title, description, inputSchema, opts, callback) {
83
111
  register(name, {
84
112
  title,
85
- description,
113
+ description: withJourney(name, description),
86
114
  inputSchema: { ...inputSchema, ...workspace },
87
115
  annotations: {
88
116
  readOnlyHint: opts.readOnly,
@@ -251,18 +279,23 @@ export function registerGameKitTools(register, options, schema = z) {
251
279
  }, { readOnly: true }, (args, extra) => api(`gamekit-projects/${enc(args.project_id)}/capabilities`, args, 'GET', extra?.signal, {
252
280
  goal: typeof args.goal === 'string' ? args.goal : undefined,
253
281
  }));
254
- tool('gripforge_game_project', 'List, create, read, bind, feed or delete Game Kit projects', 'Manage Game Kit projects (gkp_…): the container holding installed kits, their lockfile, asset bindings and data collections. action=list lists the workspace projects; create needs name and takes a preset (see gripforge_gamekit_search; adventure is the reference game of game.engine) or an explicit kits map — every new project gets game.engine first (the engine of the game: scenes, archetypes, scripts, life cycle; its answer carries the engine start pack { structure, scene, blocks, order, next, rules } and added, the kits installed for dependencies), then the default kits ui.menu (title / pause / options / load and save screen), input.remap (key remapping), settings.graphics (graphics options), ui.prompts (on-screen key prompts that follow remapping and the gamepad), ui.endscreen (end screen and credits), save.persistence (save slots, quick save, autosave), audio.core (game audio: mixer, music, effects, event → sound table), i18n.text (game texts in the player’s language: translation tables in the translations collection, ICU plurals) ui.theme (the design of every interface as kit config: colours, font, shapes, key glyphs; unset = built-in look) and input.virtualpad (the on-screen gamepad of touch screens, mode auto: shown on a touch screen without a gamepad, Touch controls tab), addedBy default: keep and configure them, leave one out ({ "ui.menu": false }) or remove it only if the user asks; get returns the full ProjectDetail (installed kits, bindings, capabilities, missing, playUrl); bind maps asset slots to Library items (lib_…, null to clear); data reads a collection or upserts / removes documents validated against the kits’ schemas (replace=true swaps the whole collection); delete needs confirm=true. Call this first to create or pick the project, then gripforge_gamekit_installed (existing project) or gripforge_gamekit_install. 0 credits.', {
255
- action: schema.enum(['list', 'create', 'get', 'bind', 'data', 'delete']).describe('list | create | get | bind | data | delete.'),
256
- project_id: projectId.optional().describe('Game Kit project id (gkp_…). Required for get, bind, data and delete.'),
282
+ tool('gripforge_game_project', 'List, create, read, bind, feed, set the controls of or delete Game Kit projects', 'Manage Game Kit projects (gkp_…): the container holding installed kits, their lockfile, asset bindings and data collections. action=list lists the workspace projects; create needs name and takes a preset (see gripforge_gamekit_search; adventure is the reference game of game.engine) or an explicit kits map — every new project gets game.engine first (the engine of the game: scenes, archetypes, scripts, life cycle; its answer carries the engine start pack { structure, scene, blocks, order, next, rules } and added, the kits installed for dependencies), then the default kits ui.menu (title / pause / options / load and save screen), input.remap (key remapping), settings.graphics (graphics options), ui.prompts (on-screen key prompts that follow remapping and the gamepad), ui.endscreen (end screen and credits), save.persistence (save slots, quick save, autosave), audio.core (game audio: mixer, music, effects, event → sound table), i18n.text (game texts in the player’s language: translation tables in the translations collection, ICU plurals) ui.theme (the design of every interface as kit config: colours, font, shapes, key glyphs; unset = built-in look) and input.virtualpad (the on-screen gamepad of touch screens, mode auto: shown on a touch screen without a gamepad, Touch controls tab), addedBy default: keep and configure them, leave one out ({ "ui.menu": false }) or remove it only if the user asks; get returns the full ProjectDetail (installed kits, bindings, capabilities, missing, playUrl); bind maps asset slots to Library items (lib_…, null to clear); data reads a collection or upserts / removes documents validated against the kits’ schemas (replace=true swaps the whole collection); controls reads or sets the controls: without scheme it returns the control schemes of the catalogue (fps, third_person, moba_click, top_down, platformer, fighting, vehicle, rts, point_click: movement by keys or by click, what the mouse is for, expected actions and keys, touch layout), the project scheme, the one its kits suggest, every action with its effective key and the keys two actions share in one mode (warnings with a proposed reassignment, never a refusal — also in get → report.controls); with scheme and dry_run=true it returns the diff of keys the scheme would write; with scheme alone it applies it (input.actions overrides marked source: scheme, the touch layout of input.virtualpad, the help bar of ui.prompts). A scheme is a starting proposal: a preset applies its own at creation (create takes controls to pick another one, or none), and any key stays changeable with gripforge_gamekit_configure input.actions { overrides: [{ action, keys, buttons?, disabled? }] } — the action of any kit; those entries always win over the scheme. delete needs confirm=true. Call this first to create or pick the project, then gripforge_gamekit_installed (existing project) or gripforge_gamekit_install. 0 credits.', {
283
+ action: schema.enum(['list', 'create', 'get', 'bind', 'data', 'controls', 'delete']).describe('list | create | get | bind | data | controls | delete.'),
284
+ project_id: projectId.optional().describe('Game Kit project id (gkp_…). Required for get, bind, data, controls and delete.'),
257
285
  name: schema.string().min(1).max(120).optional().describe('create: project name.'),
258
286
  preset: schema.string().max(80).optional().describe('create: preset id whose default kits are installed (gripforge_gamekit_search returns presets).'),
259
287
  kits: schema.record(schema.string(), schema.boolean()).optional().describe('create: explicit kit toggles { "vehicle.driveable": true, … } on top of the preset. Unchecking a required kit fails with toggle_requires. The default kits (ui.menu, input.remap, settings.graphics, ui.prompts, ui.endscreen, save.persistence, audio.core, i18n.text, ui.theme, input.virtualpad) are included unless set to false here — only when the user asked for it.'),
260
288
  target: schema.enum(['web', 'godot', 'unity', 'unreal']).optional().describe('create: pass the confirmed engine explicitly (Three.js=web). Ask the user to choose Unity / Godot / Unreal Engine / Three.js before creating if unknown. API default web is for compatibility, not an engine choice.'),
261
- bindings: schema.record(schema.string(), schema.string().nullable()).optional().describe('bind: { slot: lib_… | null } — Library item per asset slot declared by the installed kits, null clears.'),
289
+ bindings: schema.record(schema.string(), schema.string().nullable()).optional().describe('bind: { slot: lib_… | null } — Library item per asset slot declared by the installed kits, null clears. A model bound to a creature slot gets its animations by itself as far as that is free (the answer carries the chain in creatures); see gripforge_gamekit_creatures.'),
262
290
  collection: schema.string().max(80).optional().describe('data: collection name declared by an installed kit (e.g. missions, npcs, zones).'),
263
291
  documents: schema.array(schema.record(schema.string(), schema.unknown())).max(500).optional().describe('data: documents to upsert (each with its id), validated against the kit schema.'),
264
292
  remove: schema.array(schema.string().max(120)).max(500).optional().describe('data: document ids to remove.'),
265
293
  replace: schema.boolean().optional().describe('data: true to replace the whole collection with documents.'),
294
+ scheme: schema.string().max(40).optional().describe('controls: the control scheme to show (dry_run) or apply — fps, third_person, moba_click, top_down, platformer, fighting, vehicle, rts, point_click. Omit to read the schemes and the project controls.'),
295
+ dry_run: schema.boolean().optional().describe('controls: true returns the diff of keys without writing.'),
296
+ force: schema.boolean().optional().describe('controls: also replace a touch layout or a help bar the game edited (kept by default).'),
297
+ reset: schema.boolean().optional().describe('controls: also drop the key overrides the game wrote itself (kept by default), so the game ends exactly on the scheme — what a game made before the schemes needs to move to one. Preview with dry_run.'),
298
+ controls: schema.string().max(40).optional().describe('create: control scheme applied at creation instead of the preset’s own (or of the one the kits suggest); none keeps the kits’ own keys.'),
266
299
  confirm: schema.boolean().optional().describe('delete: must be true. The project, its revisions and data are removed.'),
267
300
  }, { readOnly: false }, (args, extra) => {
268
301
  const action = String(args.action);
@@ -271,13 +304,14 @@ export function registerGameKitTools(register, options, schema = z) {
271
304
  if (action === 'create') {
272
305
  if (typeof args.name !== 'string' || !args.name.trim())
273
306
  return Promise.resolve(fail('create needs name.'));
274
- return api('gamekit-projects', body(args, 'bindings', 'collection', 'documents', 'remove', 'replace'), 'POST', extra?.signal);
307
+ return api('gamekit-projects', body(args, 'bindings', 'collection', 'documents', 'remove', 'replace', 'scheme', 'dry_run', 'force', 'reset'), 'POST', extra?.signal);
275
308
  }
276
309
  if (typeof args.project_id !== 'string')
277
310
  return Promise.resolve(fail(`${action} needs project_id (gkp_…).`));
278
311
  const project = enc(args.project_id);
312
+ // creatures=1: the answer carries ranked models for every creature slot that would draw a capsule.
279
313
  if (action === 'get')
280
- return api(`gamekit-projects/${project}`, args, 'GET', extra?.signal);
314
+ return api(`gamekit-projects/${project}`, args, 'GET', extra?.signal, { creatures: 1 });
281
315
  if (action === 'bind') {
282
316
  if (!args.bindings || typeof args.bindings !== 'object')
283
317
  return Promise.resolve(fail('bind needs bindings { slot: lib_… | null }.'));
@@ -291,6 +325,18 @@ export function registerGameKitTools(register, options, schema = z) {
291
325
  return api(`gamekit-projects/${project}/data`, args, 'GET', extra?.signal, { collection: args.collection });
292
326
  return api(`gamekit-projects/${project}/data`, { workspace_id: args.workspace_id, collection: args.collection, upsert: args.documents, remove: args.remove, replace: args.replace }, 'PATCH', extra?.signal);
293
327
  }
328
+ if (action === 'controls') {
329
+ if (typeof args.scheme === 'string' && args.scheme) {
330
+ return api(`gamekit-projects/${project}/controls`, { workspace_id: args.workspace_id, scheme: args.scheme, dry_run: args.dry_run, force: args.force, reset: args.reset }, 'POST', extra?.signal);
331
+ }
332
+ return (async () => {
333
+ const [schemes, current] = await Promise.all([api('gamekits/control-schemes', args, 'GET', extra?.signal), api(`gamekit-projects/${project}/controls`, args, 'GET', extra?.signal)]);
334
+ if (current.isError)
335
+ return current;
336
+ const data = { ...current.structuredContent, schemes: schemes.structuredContent?.schemes ?? [] };
337
+ return { structuredContent: data, content: [{ type: 'text', text: JSON.stringify(data) }] };
338
+ })();
339
+ }
294
340
  if (action === 'delete') {
295
341
  if (args.confirm !== true)
296
342
  return Promise.resolve(fail('delete needs confirm=true.'));
@@ -298,6 +344,22 @@ export function registerGameKitTools(register, options, schema = z) {
298
344
  }
299
345
  return Promise.resolve(fail(`Unknown action ${action}.`));
300
346
  });
347
+ tool('gripforge_gamekit_creatures', 'Creatures of a project: a model and its animations for every unit', 'The living units of a Game Kit project (gkp_…) — player, heroes, enemies, bosses, minions, monsters, NPCs: the creature slots its kits declare. Two rules: NO creature ships as a capsule, and a model ships WITH its animations (movement AND action). action=report (default): every creature slot with its status (bound | fallback = a capsule in game), the model bound, the clips the slot plays (locomotion, action), the ones the model lacks, and its chain (steps: clone | rig | animate | more_clips, each done / running / proposed with its tool, free or its credits). action=propose: ranked Library models for each unbound slot — the workspace first, then the Community — with the reasons of the rank (source, slot and role words, game theme, size, rigged, animated, triangle budget). YOU judge: read the reasons, pick the model that fits the game (any monster beats a capsule), bind it with gripforge_game_project { action: "bind" }; nothing is final, rebind any time. A slot with no proposal at all carries defect creature_no_model: generate a model (paid — quote and ask first), never leave the capsule. action=animate: run the free steps of the chain now for the bound models (a free Community model is copied into the workspace and bound in place of the original; the standard clip pack is retargeted, 0 credits; wait=true waits for the result, else it runs in the background — read report again). action=fill: bind the best free proposal to every unbound creature slot, then animate (what the hosted creation does). Nothing here spends credits: the automatic rig, a priced Community asset and generated motions always come back as proposed steps for you to quote and ask about. Binding a model through gripforge_game_project already starts its chain. 0 credits.', {
348
+ project_id: projectId,
349
+ action: schema.enum(['report', 'propose', 'animate', 'fill']).optional().describe('report (default) | propose | animate | fill.'),
350
+ slot: schema.string().max(80).optional().describe('report / propose: only this creature slot (e.g. enemy_grunt, moba_hero_3).'),
351
+ slots: schema.array(schema.string().max(120)).max(64).optional().describe('animate: only these slots (slot or slot:variant). Default: every bound creature slot.'),
352
+ limit: schema.number().int().min(1).max(12).optional().describe('propose: models per slot (default 5).'),
353
+ wait: schema.boolean().optional().describe('animate / fill: wait for the retargets instead of running them in the background.'),
354
+ brief: schema.string().max(2000).optional().describe('fill: what the game is about, to rank the models (theme, mood).'),
355
+ }, { readOnly: false }, (args, extra) => {
356
+ const action = typeof args.action === 'string' ? args.action : 'report';
357
+ const path = `gamekit-projects/${enc(args.project_id)}/creatures`;
358
+ if (action === 'report' || action === 'propose') {
359
+ return api(path, args, 'GET', extra?.signal, { propose: action === 'propose' ? 1 : undefined, slot: typeof args.slot === 'string' ? args.slot : undefined, limit: typeof args.limit === 'number' ? args.limit : undefined });
360
+ }
361
+ return api(path, { workspace_id: args.workspace_id, action, slots: args.slots, wait: args.wait, brief: args.brief }, 'POST', extra?.signal);
362
+ });
301
363
  tool('gripforge_game_engine', 'The engine of a game: structure, bricks, report, next steps', 'Read game.engine of a Game Kit project (gkp_…) — a direction, never a gate. action=start (default): the start pack { version, active, structure { engine, kits, content counts, code }, scene, blocks digest, order (scene → playable → assets → archetypes → story → scripts → ui → validate → deliver → code), next [{ step, tool, args, why }], code (where game code goes per engine), rules } and the full content report. action=blocks: the brick catalogue computed from the manifests — entity kinds, archetype components (kit, schema, example), script verbs (conditions / actions with arguments and shorthand), script sugars (interact, enter, leave, scene, talked, quest, state, gives), quest step types, NPC behaviours, worlds, layers, events, variables; scope=catalog adds the bricks of kits not installed (install <kit>). action=report: { ok, errors, warnings, stats } — an error means that piece is left out of the game (the rest plays), a warning that it loads with something missing; each issue may carry a fix { tool, args }. action=next: the next steps only. A game with everything in code is valid: content first, kits second, code for the rest. 0 credits.', {
302
364
  project_id: projectId,
303
365
  action: schema.enum(['start', 'structure', 'blocks', 'report', 'next']).optional().describe('start (default) | structure | blocks | report | next.'),
@@ -367,6 +429,14 @@ export function registerGameKitTools(register, options, schema = z) {
367
429
  replay: schema.record(schema.string(), schema.unknown()).optional().describe('report.replay of an earlier run: plays the same inputs again (seed and budget come from it).'),
368
430
  lang: schema.enum(['en', 'fr']).optional().describe('Language of text (default en).'),
369
431
  }, { readOnly: true }, (args, extra) => api(`gamekit-projects/${enc(args.project_id)}/playtest`, body(args), 'POST', extra?.signal));
432
+ tool('gripforge_game_audit', 'Audit a game: what is left before it is finished', 'One report of everything that stands between a Game Kit project (gkp_…) and a FINISHED game of its genre — run it before telling the user a game is done, and again after each round of fixes. It aggregates, by severity (blocker / major / minor): creatures (slots drawn as a capsule, models without a skeleton, missing movement or action clips, the animation chain in progress), controls (no control scheme, the suggested one, keys shared by two actions in one mode, playable actions without a key), content (missing capabilities, required slots, the game.engine report), the last headless playtest (failures, warnings, the keys / models / animations probes, capabilities no probe covers) and completeness: what a finished game of the genre has beyond the literal request (MOBA: distinct heroes with Q/W/E/R, minions, towers, jungle monsters, shop, respawn; FPS: weapon, ammo, reload, crosshair, enemies; platformer: checkpoints, collectibles, end of level; racing: laps, ranking, AI opponents… and for every game a way to win AND to lose, title / pause / end screens, sound, a HUD, touch controls), checked against facts of the project, never a declaration. Each defect carries fix { tool, args, note, auto?, credits? }: call it as given. Returns { verdict: complete | playable_with_defects | incomplete, headline, genre, counts, defects, next (ordered calls), sections, text, play_url }. fix=true first applies what is free and safe (the best free Library model on each capsule — rebind at will —, the free standard clips, the suggested control scheme) and reports it in fixes. playtest="run" plays the game headless first (5–20 s); the default reads the last playtest and says when it is stale. Never refuses a write and never spends: paid fixes (a priced model, a rig, a generation) come back with their price to ask the user about. Aim for verdict complete; if you stop before, tell the user exactly what is left. 0 credits.', {
433
+ project_id: projectId,
434
+ fix: schema.boolean().optional().describe('true: apply the free and safe fixes first (free models on capsules, standard clips, suggested control scheme), then read. Nothing paid is ever done.'),
435
+ playtest: schema.enum(['last', 'run']).optional().describe('last (default): read the stored playtest of the project. run: play it headless now, then read.'),
436
+ brief: schema.string().max(2000).optional().describe('Free words about the game (theme, setting): they weigh in the model proposals.'),
437
+ }, { readOnly: false }, (args, extra) => (args.fix === true || args.playtest === 'run'
438
+ ? api(`gamekit-projects/${enc(args.project_id)}/audit`, body(args), 'POST', extra?.signal, undefined, 280_000)
439
+ : api(`gamekit-projects/${enc(args.project_id)}/audit`, { workspace_id: args.workspace_id }, 'GET', extra?.signal, { brief: typeof args.brief === 'string' ? args.brief : undefined })));
370
440
  tool('gripforge_moba_roster', 'Set the champions of a MOBA project', 'Turn champions into the playable roster of a moba project (preset "moba"). Pass the Library ids of 1 to 10 champions that carry an ability pack (the items made by gripforge_abilities_generate: rigged model + basic_attack and ability_q…r clips). Writes the moba_heroes and abilities collections (ids <hero>__ability_q), binds each champion to moba_hero_<n> and the player\'s champion (player, else the first) to player_character. Bots play the other champions. Returns the heroes with their attack and Q/W/E/R. Then gripforge_game_play_url to play. 0 credits.', {
371
441
  project_id: projectId,
372
442
  heroes: schema.array(schema.string()).min(1).max(10).describe('Library ids (lib_…) of champions with an ability pack, in roster order.'),
@@ -123,6 +123,8 @@ export function registerSceneTools(register, options, schema = z) {
123
123
  tool('gripforge_scene_proposal_apply', 'Apply a reviewed proposal to work', 'Explicitly accept or discard a proposal after inspecting it. Accept requires expectedRevision to match the exact base and current work revision. Atomic: concurrent changes or discard abort the save. Creates work, never promotes validated.', { id: identifier, proposal: identifier, action: schema.enum(['accept', 'discard']), expectedRevision: revision.optional() }, false, (args, extra) => call(`scenes/${args.id}/proposals/${args.proposal}`, args, 'POST', extra?.signal));
124
124
  tool('gripforge_scene_schema', 'Shared scene engine format', 'Start here. Read the shared scene model, kinds, commands, limits and an editable example. Assets are immutable revisions; placed objects are independent instances. Scene saves create work revisions, never automatic visual approval.', {}, true, (args, extra) => call('scenes/schema', args, 'GET', extra?.signal));
125
125
  tool('gripforge_scene_realism', 'Shared materials, automotive lighting and post-processing', 'Apply reusable architectural surface profiles and GTAO to a saved work scene. Read gripforge_scene_schema.realism first. assignments [{nodeId,material:"*" or a named slot,surface:{preset:preserve|brick|stone|metal|glass,detail:0..1,weathering:0..1,regions:[]}}]; surface:null removes only this local profile. Optional glass regions use explicit normalized mesh-local min/max bounds and interior parallax settings. Never guess masks from colours. ambientOcclusion {enabled,radius:0.02..3 metres,intensity:0..1,quality:low|medium|high}, or null to remove. Preserves transforms, source PBR maps, other instances and validated revisions. Returns Studio link for visual comparison. Optional quality {preset:performance|balanced|quality,resolutionScale:0.5..1,sharpness:0..0.5}, or null to restore the original presentation. Quality supports viewport resolution up to a bounded 4K budget. Optional automotive {look:studio|daylight,quality,lengthM,depthOfField:false,focusDistanceM} explicitly applies a whole-scene look: HDR strip reflections, contact AO, quality and restrained post-processing. Studio replaces an existing HDRI; daylight keeps it. Optional colorGrading/depthOfField overrides or null. DOF is perspective/capture only, disabled with volumetric passes. No generation charge, DLSS or automatic approval.', { id: identifier, expectedRevision: revision, assignments: schema.array(schema.object({ nodeId: identifier, material: schema.string().min(1).max(256).optional(), surface: record.nullable() })).max(100).optional(), ambientOcclusion: schema.object({ enabled: schema.boolean(), radius: schema.number().min(.02).max(3), intensity: schema.number().min(0).max(1), quality: schema.enum(['low', 'medium', 'high']) }).nullable().optional(), quality: schema.object({ preset: schema.enum(['performance', 'balanced', 'quality']), resolutionScale: schema.number().min(.5).max(1).optional(), sharpness: schema.number().min(0).max(.5).optional() }).nullable().optional(), automotive: schema.object({ look: schema.enum(['studio', 'daylight']).optional(), quality: schema.enum(['performance', 'balanced', 'quality']).optional(), lengthM: schema.number().min(1).max(25).optional(), ground: schema.boolean().optional(), depthOfField: schema.boolean().optional(), focusDistanceM: schema.number().min(.1).max(1000).optional() }).optional(), colorGrading: schema.object({ contrast: schema.number().min(.5).max(1.5), saturation: schema.number().min(0).max(1.5), temperature: schema.number().min(-1).max(1) }).nullable().optional(), depthOfField: schema.object({ enabled: schema.boolean(), focusDistanceM: schema.number().min(.1).max(1000), aperture: schema.number().min(0).max(.01), maxBlur: schema.number().min(0).max(.02) }).nullable().optional() }, false, (args, extra) => call(`scenes/${args.id}/realism`, args, 'POST', extra?.signal));
126
+ tool('gripforge_apply_realism', 'Orchestrate scene realism', 'Plan or apply render.realism across shared PBR materials, immutable textures, surface shaders, lighting, post-processing, environment and skinned character rendering, with a bounded render quality and render.lod configuration suggestion. Defaults to mode=plan (no save); mode=apply writes one work revision atomically, preserving assets, transforms, animation and validated versions. Explicit nodeIds restrict instance assignments; lighting/post-processing are global. materials/characters rows use {nodeId,material:"*"|exactName,override:{roughness,metalness,map,normalMap,surface,...}}; pinned map references, null removes overrides. Characters require character/enemy/boss/fps-arms role. Does not fabricate missing detail, execute paid generation, add game LOD automatically, or approve realism visually. Request a real scene_review afterwards. WEB only; not DLSS.', { id: identifier, expectedRevision: revision, mode: schema.enum(['plan', 'apply']).default('plan'), quality: schema.enum(['performance', 'balanced', 'quality']).optional(), lighting: schema.enum(['preserve', 'daylight', 'golden_hour', 'overcast', 'night']).optional(), nodeIds: schema.array(identifier).min(1).max(100).optional(), materials: schema.array(schema.object({ nodeId: identifier, material: schema.string().min(1).max(256).optional(), override: record })).max(100).optional(), characters: schema.array(schema.object({ nodeId: identifier, material: schema.string().min(1).max(256).optional(), override: record })).max(100).optional(), environment: record.optional(), postProcess: record.optional() }, false, (args, extra) => call(`scenes/${args.id}/render-realism`, args, 'POST', extra?.signal));
127
+ tool('gripforge_realism_schema', 'Read realism orchestration contract', 'Read render.realism systems, modes, bounds and immutable-source workflow for an accessible scene.', { id: identifier }, true, (args, extra) => call(`scenes/${args.id}/render-realism`, args, 'GET', extra?.signal));
126
128
  tool('gripforge_scene_optimize', 'Prepare a scene for fast browser loading', 'Queue resumable browser texture variants for a saved scene. Preserve original GLBs, geometry, transforms, PBR, animations and Library heads. Color textures up to 1024px, data maps up to 512px and terrain color up to 4096px; WebP encoding. Saves a new work revision only if expectedRevision still matches. Does not promote validated. Poll generation_read for byte savings and studio_url. 0 credits; storage quota applies. New Unreal imports do this automatically.', { id: identifier, expectedRevision: revision, idempotency_key: schema.string().min(8).max(160).optional() }, false, (args, extra) => call(`scenes/${args.id}/optimize`, args, 'POST', extra?.signal));
127
129
  tool('gripforge_scene_list', 'List workspace scenes', 'List scenes, work/validated revision pointers and links in the authenticated workspace.', {}, true, (args, extra) => call('scenes', args, 'GET', extra?.signal));
128
130
  tool('gripforge_scene_read', 'Read scene source', 'Read the editable SceneDocument at work, validated, or a numbered revision. Keep work_revision for optimistic writes. Validated can be absent.', { id: identifier, version: schema.union([schema.enum(['work', 'validated']), schema.number().int().positive()]).optional() }, true, (args, extra) => call(`scenes/${encodeURIComponent(String(args.id))}?version=${args.version ?? 'work'}`, args, 'GET', extra?.signal));
package/dist/server.js CHANGED
@@ -1095,7 +1095,7 @@ server.tool('gripforge_hand_check', 'Verify that a rigged character\'s fingers f
1095
1095
  return err(String(data.error ?? res.statusText));
1096
1096
  return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
1097
1097
  });
1098
- server.tool('gripforge_animate', 'Retarget the standard clip pack onto a Mixamo-named Library character/enemy/armed bind. Base slots (idle, walk, run, attack1-3, hit, death, dodge) + extras (taunt, stinger, knockback, block, jump_start/loop/land, idle_guns, shoot, reload, aim, crouch_idle, crouch_walk, knife). archetype remaps the base slots: sword (default), claws (zombie scratch), heavy (slow + heavy combo), brawler (punches), puppet (stiff walk + throws), operator (CS-style: knife/punch strikes). Opt-in slots fire_breath (mouth exhalation) and energy_cast (two-hand azure wave) use the Studio VFX character motions. Saves kind=animation. 0 credits.', {
1098
+ server.tool('gripforge_animate', 'Retarget the standard clip pack onto a Mixamo-named Library character/enemy/armed bind. In a Game Kit project gripforge_gamekit_creatures runs it for free on every bound creature, and gripforge_game_audit lists the models still lacking movement or action clips. Base slots (idle, walk, run, attack1-3, hit, death, dodge) + extras (taunt, stinger, knockback, block, jump_start/loop/land, idle_guns, shoot, reload, aim, crouch_idle, crouch_walk, knife). archetype remaps the base slots: sword (default), claws (zombie scratch), heavy (slow + heavy combo), brawler (punches), puppet (stiff walk + throws), operator (CS-style: knife/punch strikes). Opt-in slots fire_breath (mouth exhalation) and energy_cast (two-hand azure wave) use the Studio VFX character motions. Saves kind=animation. 0 credits.', {
1099
1099
  character_id: z.string().describe('Library character, enemy, or armed bind'),
1100
1100
  archetype: z.enum(['sword', 'claws', 'heavy', 'brawler', 'puppet', 'operator']).optional().describe('Movement/strike archetype — remaps the base slots (default sword); operator = CS-style (knife/punch strikes, guns via idle_guns/shoot/reload/aim, crouch_idle/crouch_walk)'),
1101
1101
  slots: z
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gripforgeai/mcp",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "The tools your AI needs to make your games. Turn prompts into production-ready game assets — animated characters with their weapons attached, seamless textures, terrain, VFX, HUDs — and playable game kits Unity, Godot, Unreal or Three.js can load.",
5
5
  "license": "MIT",
6
6
  "type": "module",