@gripforgeai/mcp 0.1.11 → 0.1.12
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/LICENSE +21 -0
- package/README.md +3 -194
- package/dist/architecture-tools.js +13 -3
- package/dist/gamekit-tools.js +9 -3
- package/dist/look.js +6 -0
- package/dist/scene-tools.js +5 -3
- package/dist/server.js +15 -31
- package/dist/vfx-project-tools.js +2 -1
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 GripForge
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ claude mcp add --transport http gripforge https://gripforge.ai/mcp --header "x-a
|
|
|
56
56
|
- **Playable game kits.** Assemble modular capabilities — movement, combat, enemies, worlds — into
|
|
57
57
|
a game that runs in the browser, then bind your own assets to it.
|
|
58
58
|
- **One Library.** Everything generated lands in a locker your engine can pull from, and that your
|
|
59
|
-
agent can search by look ("Devil May Cry like", "
|
|
59
|
+
agent can search by look ("Devil May Cry like", "genshin").
|
|
60
60
|
|
|
61
61
|
## Use cases
|
|
62
62
|
|
|
@@ -172,15 +172,12 @@ writes files into the repo (`out_dir`), including `gripforge_hud`,
|
|
|
172
172
|
`gripforge_hud_bar` and `gripforge_cape`. Hosted-only: `gripforge_make_seamless`.
|
|
173
173
|
Full list: https://gripforge.ai/mcp-docs
|
|
174
174
|
|
|
175
|
-
- `gripforge_style_kit` — resolve "Devil May Cry like" / "
|
|
175
|
+
- `gripforge_style_kit` — resolve "Devil May Cry like" / "genshin" to locker ids
|
|
176
176
|
already tagged with that look. **Call this before generating.** Reuse the ids.
|
|
177
177
|
- `gripforge_generate_character` — new T-pose + auto-rig into Library (10 credits).
|
|
178
178
|
`kind=enemy` or the word "enemy" in the prompt. Skip this if style_kit already
|
|
179
179
|
returned a character.
|
|
180
|
-
- `gripforge_boss` — boss
|
|
181
|
-
- `gripforge_abilities` — reusable `combat.abilities` skills: `schema`, `example`, `validate`, `export`; executable adapters for Three.js, Godot, Unity and Unreal. Common timing/cost/cooldown/targeting; connect the game's combat and presentation backend. No rig or animation manufacture. Persist through gamekit config/data. 0 generation credits. See [abilities documentation](../../docs/abilities.md).
|
|
182
|
-
- `gripforge_creature_rig_schema`, `gripforge_creature_analyze`, `gripforge_creature_rig` — reusable anatomy detection and Blender rigging for owned GLBs. Four annotated views → editable anatomy → new private rigged draft, starting clips, GLB, .blend and deformation report. Persistent progress/cancel/retry; source and validated assets stay intact. Review uncertain anatomy and inspect the result in Character Studio. [Creature rig documentation](../../docs/creature-rig.md).
|
|
183
|
-
- `gripforge_architecture_schema`, `gripforge_generate_building`, `gripforge_generate_district` — free plan, optional building concepts (1–4 views conditioned on one master, saved to the workspace), visual review, then Meshy PBR manufacture. Separate concept/3D budgets; owned single/multi-image references, configurable geometry/texture quality, immutable Library assets and editable instances. Persistent progress/cancel/retry and Studio links; private work versions. [Architecture documentation](../../docs/architecture-generation.md).
|
|
180
|
+
- `gripforge_boss` — playable boss kit (stats, phases, attacks, arena, engine snippets).
|
|
184
181
|
0 credits. Reuses a locker enemy. `generate=true` forges a new kind=enemy (10 credits).
|
|
185
182
|
Then `gripforge_animate` with the returned archetype.
|
|
186
183
|
- `gripforge_concept_correct` — concept image → strict T-pose sheet (1 credit).
|
|
@@ -208,7 +205,6 @@ Full list: https://gripforge.ai/mcp-docs
|
|
|
208
205
|
- `gripforge_scene_kit` — locker props + suggested layout for a game look.
|
|
209
206
|
- `gripforge_light_kit` / `gripforge_font` / `gripforge_navmesh` — lights+fog, webfont+ranks, walkable AABB. 0 credits.
|
|
210
207
|
- `gripforge_input_kit` — FPS InputMap (WASD + arrows). Write input.json, autoload GfInput.
|
|
211
|
-
- `gripforge_joystick_kit` — ready-to-integrate touch/mouse joystick files for Unreal, Godot, Unity and Three.js. Independent movement/look sticks, radial deadzone, continuous hold and focus/release reset. Free, shared with hosted MCP; see below.
|
|
212
208
|
Optional `third_person: true` adds portable character-facing JavaScript and a
|
|
213
209
|
Three.js integration example; the existing input map is unchanged. 0 credits.
|
|
214
210
|
- `gripforge_viewmodel_kit` — CS-style FPS arms + gun under Camera3D/Hold. Write viewmodel.json + gf_viewmodel.gd. 0 credits.
|
|
@@ -252,10 +248,6 @@ Full parameters: https://gripforge.ai/mcp-docs
|
|
|
252
248
|
- `gripforge_game_capabilities` — what the project provides, what is missing for a goal and which kits fill each gap.
|
|
253
249
|
- `gripforge_game_project` — `action=list|create|get|bind|data|delete` on projects (bindings slot → `lib_…`, data collections, `confirm=true` to delete).
|
|
254
250
|
- `gripforge_game_play_url` — browser play URL of the project's current revision (`{ url, absolute }`).
|
|
255
|
-
- `gripforge_moba_map` — the map of a MOBA project as a gameplay plan: generate (1–5 lanes, divider), validate (lanes open, everything reachable), set.
|
|
256
|
-
- `gripforge_ability_vfx` — a cast effect written and bound for every ability of a project (needs `fx.ability_vfx`; 0 credits).
|
|
257
|
-
- `gripforge_terrain_map_use` — a terrain studio map (`lvl_…`) or a scene terrain becomes the world of a game with `world.terrain`: baked file bound to `terrain_map`, reference in `terrain_maps`.
|
|
258
|
-
- `gripforge_moba_roster` — champions with an ability pack become the roster of a moba project (`moba_heroes`, `abilities`, `moba_hero_<n>` and `player_character` bindings).
|
|
259
251
|
|
|
260
252
|
## Character facing for third-person games
|
|
261
253
|
|
|
@@ -286,22 +278,6 @@ network systems. Omitting `third_person`, or setting it to `false`, returns the
|
|
|
286
278
|
existing input kit without the additional section. The option is available in
|
|
287
279
|
the hosted MCP and this source checkout; it is not in published npm `0.1.5`.
|
|
288
280
|
|
|
289
|
-
## Asset production for games
|
|
290
|
-
|
|
291
|
-
`gripforge_asset_search` searches real Library and Community assets, independently
|
|
292
|
-
of the Game Kit catalogue. Filter by visual role (`q`), `kind`, `source` and the
|
|
293
|
-
connected `workspace_id`. Search is free. `gripforge_asset_clone` copies a suitable
|
|
294
|
-
Community item into the workspace; the first take costs one generation credit
|
|
295
|
-
(one per piece for an armor set).
|
|
296
|
-
|
|
297
|
-
Generate only missing roles. Keep a stable `idempotency_key` for each generation,
|
|
298
|
-
save its returned `job_id`, poll `gripforge_generation_read`, then download and
|
|
299
|
-
integrate the completed asset in the actual engine. A queued job is not a model.
|
|
300
|
-
The hosted generation responses preserve job ids and progress/Studio URLs.
|
|
301
|
-
|
|
302
|
-
These additions are available in the hosted MCP and this source checkout.
|
|
303
|
-
Updating the hosted service does not update an installed npm package.
|
|
304
|
-
|
|
305
281
|
## Performance diagnostics
|
|
306
282
|
|
|
307
283
|
`gripforge_performance` is included in this source checkout and the hosted MCP.
|
|
@@ -348,174 +324,7 @@ CPU/driver time already included in `cpuMs`; `gpuMs` is meaningful only when
|
|
|
348
324
|
actually measured. This tool does not control a browser, change game settings,
|
|
349
325
|
publish code or automatically fix a game.
|
|
350
326
|
|
|
351
|
-
## Concept-first maps and Unreal references
|
|
352
|
-
|
|
353
|
-
`gripforge_map_schema` describes the shared Map Studio generation workflow.
|
|
354
|
-
`gripforge_map_generate` supports two separate persistent jobs:
|
|
355
|
-
|
|
356
|
-
1. `stage: "concept"` saves a private master image and returns its immutable
|
|
357
|
-
`{assetId, revisionId}` reference. Optional `reference` is a pinned Library
|
|
358
|
-
image of an existing environment: it guides the art direction of a **new**
|
|
359
|
-
layout. Use `gripforge_scene_asset_pin` after importing a viewport screenshot.
|
|
360
|
-
Request `views: ["overview", "top_down", "entrance", "objective"]` for a
|
|
361
|
-
complete concept set. Every additional angle is conditioned on that same
|
|
362
|
-
pinned master. The overview is always included; omitting `views` keeps the
|
|
363
|
-
single-image workflow.
|
|
364
|
-
2. Poll `gripforge_generation_read` with `include_preview: true` to receive all
|
|
365
|
-
available views, or add `preview_view: "entrance"` to see just one. Show the
|
|
366
|
-
concept to the user. These illustrations convey the intended appearance;
|
|
367
|
-
they do not establish exact geometry, scale or visibility. Verify those with
|
|
368
|
-
camera captures from the same built 3D scene.
|
|
369
|
-
3. After selection, submit a new `stage: "build"` request with the returned
|
|
370
|
-
`concept`, a new idempotency key and the same workspace. The result contains
|
|
371
|
-
editable terrain, regions, paths, zones and independent asset instances in
|
|
372
|
-
the shared Scene Engine. `sources` can select `workspace`, `community` and/or
|
|
373
|
-
`catalog` resources. Inspect the actual scene before requesting visual review.
|
|
374
|
-
|
|
375
|
-
Each newly generated concept image costs 1 credit: one for the master, up to
|
|
376
|
-
three for additional views. To add angles without regenerating the chosen
|
|
377
|
-
master, use `source_concept: {assetId, revisionId}` instead of `reference` in a
|
|
378
|
-
new concept request. Reusing that master costs nothing; only new views are
|
|
379
|
-
billed. A build job costs 1 credit. Cancellation and retry retain each completed
|
|
380
|
-
view and avoid repeating successful image generation or billing. Neither stage
|
|
381
|
-
promotes the validated version. Omitting `stage` preserves the existing build workflow.
|
|
382
|
-
`concept_item` remains a legacy Library image shortcut; prefer `concept` to pin
|
|
383
|
-
the precise chosen image even if the Library source is later replaced.
|
|
384
|
-
|
|
385
|
-
```json
|
|
386
|
-
{
|
|
387
|
-
"stage": "concept",
|
|
388
|
-
"prompt": "A new desert canyon for third-person melee combat, two open arenas and a raised shortcut",
|
|
389
|
-
"wizard": {
|
|
390
|
-
"style": "stylized", "world": "linear", "terrain": "desert_canyon",
|
|
391
|
-
"size": "medium", "dimensions": { "width": 500, "depth": 500 },
|
|
392
|
-
"boundary": "fixed", "spawn": "single", "boss": true
|
|
393
|
-
},
|
|
394
|
-
"reference": { "assetId": "lib_your_image", "revisionId": "rev_pinned_image" },
|
|
395
|
-
"views": ["overview", "top_down", "entrance", "objective"],
|
|
396
|
-
"idempotency_key": "desert-concept-01"
|
|
397
|
-
}
|
|
398
|
-
```
|
|
399
|
-
|
|
400
|
-
The result exposes `concept_views` with each view's pinned `asset`, `image_url`
|
|
401
|
-
and authenticated `preview_url`. `concept` and the suggested build request keep
|
|
402
|
-
the master reference. To extend an existing concept, keep the prompt and wizard,
|
|
403
|
-
replace `reference` with `source_concept`, list the additional views and use a new
|
|
404
|
-
idempotency key. These options are part of this source checkout; they require a
|
|
405
|
-
matching deployed API and an updated MCP client before use in production.
|
|
406
|
-
|
|
407
|
-
`wizard.dimensions` sets width and depth in **metres** (100–5,000 each), overriding
|
|
408
|
-
the size preset and world-type multiplier. Carry the same dimensions from concept
|
|
409
|
-
to build. Explicitly dimensioned outdoor terrain uses those exact edge-to-edge
|
|
410
|
-
bounds; decorative backdrops can extend beyond the playable footprint. Four-team
|
|
411
|
-
maps require a square. Concept images communicate the intended scale; they are
|
|
412
|
-
not a measured reconstruction. Omitting dimensions preserves legacy framing.
|
|
413
|
-
|
|
414
|
-
For an existing Unreal project, the source checkout also provides
|
|
415
|
-
`gripforge_map_unreal_reference_local`:
|
|
416
|
-
|
|
417
|
-
```json
|
|
418
|
-
{
|
|
419
|
-
"project_file": "/absolute/path/Game/Game.uproject",
|
|
420
|
-
"content_path": "/Game/StylizedDesertEnv"
|
|
421
|
-
}
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
It inventories only the selected pack and returns native asset paths. It does
|
|
425
|
-
not upload packages, decode mesh geometry, read editor actor transforms or
|
|
426
|
-
write to the project. Kinds are directory hints until verified in Unreal.
|
|
427
|
-
The hosted MCP cannot access a user's local disk. Build this checkout and run
|
|
428
|
-
`packages/mcp-client/dist/server.js` to use the local tool; changing source code
|
|
429
|
-
or deploying the website does not update an installed npm package.
|
|
430
|
-
|
|
431
|
-
Native delivery uses the reusable `gripforge.unreal-scene.v1` adapter after concept
|
|
432
|
-
selection. It consumes the **common SceneDocument**, a `.uproject`, a new level
|
|
433
|
-
path, and pinned asset revision bindings. Map-specific layouts, asset choices and
|
|
434
|
-
lighting values belong to the recipe, not the importer.
|
|
435
|
-
|
|
436
|
-
For explicit 3D layouts, `gripforge_map_generate` also accepts `structure` with
|
|
437
|
-
schema `gripforge.map-structures.v1`. Read `gripforge_map_schema` for a complete
|
|
438
|
-
example. Terraces carry polygons and elevations; ramps/bridges name their
|
|
439
|
-
endpoints, widths and slope/clearance limits. The common builder preserves canyon
|
|
440
|
-
voids, rejects buried connections and creates separate surface/cliff/bridge
|
|
441
|
-
assets with persistent per-piece checkpoints. Match `wizard.dimensions`; this
|
|
442
|
-
branch currently supports solo maps with spawn and optional objective. Results
|
|
443
|
-
are structural work versions requiring visual review and dressing. Existing
|
|
444
|
-
callers without `structure` keep their previous generation pipeline.
|
|
445
|
-
|
|
446
|
-
1. `gripforge_map_unreal_prepare_local({plan_file, job_directory})` validates and
|
|
447
|
-
snapshots the scene/bindings and packages the installed Python worker.
|
|
448
|
-
2. `gripforge_map_unreal_import_local({job_directory, editor_executable})` launches
|
|
449
|
-
the local UE editor; the persistent job continues after the MCP call ends.
|
|
450
|
-
3. `gripforge_map_unreal_import_status_local({job_directory})` reads actual progress,
|
|
451
|
-
errors, measurements and viewport capture paths.
|
|
452
|
-
4. `gripforge_map_unreal_import_cancel_local({job_directory})` requests cancellation
|
|
453
|
-
at a saved checkpoint. Close that editor, then import the same job to resume.
|
|
454
|
-
|
|
455
|
-
The worker creates a **new work level** under `/Game/GripForge/Maps/`, reuses native
|
|
456
|
-
meshes/PBR materials, imports per-asset GLBs and preserves instance transforms,
|
|
457
|
-
hierarchy and IDs. Paths/region/zone outlines become editable splines. Player
|
|
458
|
-
starts bind spawn zones; the full scene data is also carried on actor tags.
|
|
459
|
-
Coordinate conversion is metres/Y-up to centimetres/Z-up, including native pivot
|
|
460
|
-
offsets and rotations. Material overrides affect components, not shared source
|
|
461
|
-
meshes. Terrain geometry has a size check and complex collision on its generated
|
|
462
|
-
asset. Source packages are not rewritten. A changed scene needs a new job and
|
|
463
|
-
level revision; reopening the same saved job does not duplicate completed actors.
|
|
464
|
-
|
|
465
|
-
See `docs/unreal-scene-import.md` in the source
|
|
466
|
-
repository for the manifest contract and CLI. The worker is shipped in the MCP
|
|
467
|
-
package's `runtime/` folder. It requires the full Unreal editor with Python and
|
|
468
|
-
Editor Scripting Utilities, tested against UE 5.7. It is not a headless web service.
|
|
469
|
-
The current adapter handles static map content and native Blueprint props;
|
|
470
|
-
skeletal retargeting, animation/attachments, terrain layer authoring as native
|
|
471
|
-
Landscape, navigation baking, HDRI/cloud translation and bidirectional edits
|
|
472
|
-
need their own adapters. Unsupported features fail explicitly. A generated
|
|
473
|
-
terrain is an editable static-mesh actor, not a sculptable Landscape.
|
|
474
|
-
|
|
475
|
-
Successful import ends at **awaiting_visual_review**, not validated/current.
|
|
476
|
-
Review the actual UE render, collision and gameplay before accepting that work
|
|
477
|
-
version. The legacy `gripforge_map_export` merged FPS GLB is not this native import.
|
|
478
|
-
|
|
479
327
|
## Env
|
|
480
328
|
|
|
481
329
|
- `GRIPFORGE_API_KEY` (required) — 1 credit = 1 successful attach
|
|
482
330
|
- `GRIPFORGE_API_URL` (optional) — defaults to https://gripforge.ai
|
|
483
|
-
|
|
484
|
-
### Joystick for Unreal, Godot, Unity and Three.js
|
|
485
|
-
|
|
486
|
-
Call `gripforge_joystick_kit` with:
|
|
487
|
-
|
|
488
|
-
```json
|
|
489
|
-
{"target":"unreal","layout":"dual","deadzone":0.15,"radius":72,"accent":"#ff681f"}
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
`target` also accepts `godot`, `unity`, `threejs` or `all`. The response includes
|
|
493
|
-
complete `deliveries[].files[]` (relative path + source) and a README per engine.
|
|
494
|
-
Write those files into the project and follow that README. No asset generation,
|
|
495
|
-
API key or credit is required. Both hosted and npm MCP expose the same tool.
|
|
496
|
-
|
|
497
|
-
Unreal receives a runtime Pawn component/plugin; Godot a CanvasLayer scene;
|
|
498
|
-
Unity a UGUI component; Three.js a DOM overlay and camera-relative input example.
|
|
499
|
-
Movement and optional camera sticks own independent fingers, hold their axes
|
|
500
|
-
continuously, and reset on release/cancel/focus loss. The radial deadzone preserves
|
|
501
|
-
analog magnitude. Existing movement physics and physical gamepad bindings stay
|
|
502
|
-
with the game. Installation and actual game verification are separate from file
|
|
503
|
-
generation; Unreal source must be compiled for the project's UE version.
|
|
504
|
-
# Unreal environments in Map Studio
|
|
505
|
-
|
|
506
|
-
The local tools `gripforge_map_unreal_export_local`,
|
|
507
|
-
`gripforge_map_unreal_export_status_local`, `gripforge_map_unreal_export_cancel_local`
|
|
508
|
-
and `gripforge_map_unreal_upload_local` export a saved Unreal map and import it into
|
|
509
|
-
the authenticated workspace. Supply the installed UE 5.7 editor, `.uproject`,
|
|
510
|
-
`/Game/...` level and a persistent job directory. Per-file progress survives closed
|
|
511
|
-
calls; upload returns a GripForge Studio link after the persistent server job completes.
|
|
512
|
-
The source project is not saved. Imported scenes remain private work versions.
|
|
513
|
-
|
|
514
|
-
The hosted equivalents `gripforge_map_import_unreal_schema` and
|
|
515
|
-
`gripforge_map_import_unreal` accept exported common scene data, not native `.umap`
|
|
516
|
-
bytes. The hosted service cannot start a local editor. PBR meshes, transforms,
|
|
517
|
-
foliage patches and Landscape data are portable; Blueprints, Niagara and custom
|
|
518
|
-
water/sky shaders need separate adapters. Foliage patches are edited as groups.
|
|
519
|
-
Read the live contract at `GET /api/v1/maps/import/unreal` and the public MCP/API docs.
|
|
520
|
-
|
|
521
|
-
`gripforge_generation_quote` (hosted MCP / source client) estimates model, concept, texture, rig or animation operations before spending. It returns subscription/credit funding, remaining usage and an operation estimate. Propose missing assets and obtain a generation budget; a quote for one operation is not a fixed price for an entire game.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
|
-
|
|
2
|
+
import { LOOK_DESCRIPTION, LOOK_VALUES } from './look.js';
|
|
3
|
+
export const ARCHITECTURE_TOOL_NAMES = ['gripforge_architecture_schema', 'gripforge_generate_building', 'gripforge_generate_district', 'gripforge_environment_module'];
|
|
3
4
|
/** Hosted and local MCP share the same recipe and durable server workflow. */
|
|
4
5
|
export function registerArchitectureTools(register, options, schema = z) {
|
|
5
6
|
const identifier = schema.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,159}$/);
|
|
@@ -8,6 +9,7 @@ export function registerArchitectureTools(register, options, schema = z) {
|
|
|
8
9
|
id: schema.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,47}$/).optional(),
|
|
9
10
|
name: schema.string().min(1).max(100).optional(), prompt: schema.string().min(3).max(300),
|
|
10
11
|
style: schema.enum(['realistic', 'stylized', 'lowpoly', 'handpainted']).optional(),
|
|
12
|
+
look: schema.enum(LOOK_VALUES).optional().describe(`${LOOK_DESCRIPTION} Fills style when style is omitted; toon, anime and pixel add their phrase to the prompt.`),
|
|
11
13
|
use: schema.enum(['residential', 'retail', 'office', 'industrial', 'mixed']).optional(),
|
|
12
14
|
floors: schema.number().int().min(1).max(30).optional(),
|
|
13
15
|
dimensions: schema.object({ width: schema.number().min(3).max(100).optional(), depth: schema.number().min(3).max(100).optional(), height: schema.number().min(3).max(150).optional() }).strict().optional().describe('Maximum footprint and height in metres. Uniform fit preserves proportions; actual dimensions are returned.'),
|
|
@@ -32,6 +34,13 @@ export function registerArchitectureTools(register, options, schema = z) {
|
|
|
32
34
|
workspace_id: schema.string().max(100).optional(),
|
|
33
35
|
idempotency_key: schema.string().regex(/^[a-zA-Z0-9_.:-]{8,160}$/).optional(),
|
|
34
36
|
};
|
|
37
|
+
const moduleRecipe = schema.object({
|
|
38
|
+
module: schema.enum(['pillar', 'arch', 'fountain', 'stairs', 'wall', 'pavement', 'rock', 'cliff', 'ruin', 'bridge', 'watchtower', 'shrine', 'guardian']),
|
|
39
|
+
width: schema.number().min(.3).max(40).optional(), depth: schema.number().min(.3).max(40).optional(), height: schema.number().min(.05).max(20).optional(),
|
|
40
|
+
seed: schema.number().int().min(0).max(2147483646).optional(),
|
|
41
|
+
pattern: schema.enum(['radial', 'botanical']).optional().describe('Pavement only: botanical adds a broad leaf rosette inlay, radial keeps the original restrained rings.'),
|
|
42
|
+
palette: schema.object({ stone: schema.string().regex(/^#[0-9a-f]{6}$/i).optional(), recess: schema.string().regex(/^#[0-9a-f]{6}$/i).optional(), trim: schema.string().regex(/^#[0-9a-f]{6}$/i).optional(), water: schema.string().regex(/^#[0-9a-f]{6}$/i).optional() }).strict().optional(),
|
|
43
|
+
}).strict();
|
|
35
44
|
const definitions = [
|
|
36
45
|
{ name: ARCHITECTURE_TOOL_NAMES[0], kind: 'schema', shape: {}, title: 'Architecture · schema', description: 'Read the reusable Meshy building and district contract, example, limits and plan → build workflow. Separate immutable Library assets, scene instances and shared SceneDocument. No paid generation.' },
|
|
37
46
|
{ name: ARCHITECTURE_TOOL_NAMES[1], kind: 'building', shape: { ...shared, recipe: building,
|
|
@@ -42,6 +51,7 @@ export function registerArchitectureTools(register, options, schema = z) {
|
|
|
42
51
|
concept_views: schema.array(schema.enum(['front_right', 'front_left', 'rear_right', 'rear_left'])).min(1).max(4).optional().describe('Unique views starting with front_right. Default one view for scene, three for isolated. Alternate views edit the SAME master. recipe.concept reuses an existing master; concept_reference guides a NEW master.'),
|
|
43
52
|
}, title: 'Generate a building', description: 'Reusable plan → artistic concept → isolated reference views → review → Meshy build workflow. Generate 1–4 coherent images through OpenAI (1536×1024 high quality, default) or Imagine (2K), saved as private Library drafts; alternate views reference one master. Separate explicit budgets for concepts and 3D. Accepts text, owned single/multiple concept views or an owned static GLB. Meshy PBR/geometry quality, bounded geometry and uniform metric fit. Durable jobs return workspace links; poll generation_read, cancel/retry preserves finished steps. Review images before building and the real Studio render before publishing. No guaranteed interiors, collisions or LODs. Never substitutes procedural geometry or automatically replaces a game asset.' },
|
|
44
53
|
{ name: ARCHITECTURE_TOOL_NAMES[2], kind: 'district', shape: { ...shared, recipe: district }, title: 'Generate a district', description: 'Plan then build a straight-street district from 1–8 distinct Meshy buildings, each manufactured once and reused as separate editable instances. Includes road, pavements, spawn, daylight and camera in the shared Map SceneDocument. Optional owned PBR road/pavement maps; otherwise simple solid surfaces. Plan returns layout, provider-credit count and account quote without spending. Build requires a budget and returns a persistent job, then Map Studio link. Private work version requiring visual review; no automatic Community publication or game replacement.' },
|
|
54
|
+
{ name: ARCHITECTURE_TOOL_NAMES[3], kind: 'module', shape: { stage: schema.enum(['inspect', 'build']).optional(), recipe: moduleRecipe, name: schema.string().min(1).max(100).optional(), workspace_id: schema.string().max(100).optional() }, title: 'Create a reusable environment module', description: 'Free parametric 3D masonry, independent of any game: pillar, arch, fountain, stairs, wall, pavement, rock, cliff, ruin, bridge, watchtower, shrine or a stone guardian. inspect (default) returns real generated bounds, triangle count, metric recipe and assembly sockets. build saves a private textured GLB in the Library, with bevelled geometry, baked vertex shading and named stone/recess/trim/water materials. Dimensions are nominal metres; bounds report actual moulding overhang. Ground origin, stairs ascend toward -Z, <=23 cm risers; top/bottom sockets support assembly. Seed and palette are repeatable. Does not place assets, generate arbitrary AI models, publish, or guarantee navigation/collisions in a target game. 0 credits.' },
|
|
45
55
|
];
|
|
46
56
|
for (const tool of definitions)
|
|
47
57
|
register(tool.name, { title: tool.title, description: tool.description, inputSchema: tool.shape,
|
|
@@ -54,10 +64,10 @@ export function registerArchitectureTools(register, options, schema = z) {
|
|
|
54
64
|
return { isError: true, content: [{ type: 'text', text: 'GripForge API key required.' }] };
|
|
55
65
|
const { workspace_id, ...body } = parsed.data;
|
|
56
66
|
try {
|
|
57
|
-
const response = await fetch(options.apiUrl.replace(/\/$/, '') + '/api/v1/architecture', {
|
|
67
|
+
const response = await fetch(options.apiUrl.replace(/\/$/, '') + (tool.kind === 'module' ? '/api/v1/environment-modules' : '/api/v1/architecture'), {
|
|
58
68
|
method: tool.kind === 'schema' ? 'GET' : 'POST',
|
|
59
69
|
headers: { 'content-type': 'application/json', 'x-gripforge-client': 'mcp', ...(key ? { 'x-api-key': key } : {}), ...(typeof workspace_id === 'string' ? { 'x-workspace-id': workspace_id } : {}) },
|
|
60
|
-
...(tool.kind === 'schema' ? {} : { body: JSON.stringify({ ...body, kind: tool.kind }) }),
|
|
70
|
+
...(tool.kind === 'schema' ? {} : { body: JSON.stringify(tool.kind === 'module' ? body : { ...body, kind: tool.kind }) }),
|
|
61
71
|
signal: AbortSignal.any([AbortSignal.timeout(60_000), ...(extra?.signal ? [extra.signal] : [])]),
|
|
62
72
|
});
|
|
63
73
|
const data = await response.json();
|
package/dist/gamekit-tools.js
CHANGED
|
@@ -369,9 +369,15 @@ export function registerGameKitTools(register, options, schema = z) {
|
|
|
369
369
|
const { project_id, ...body } = args;
|
|
370
370
|
return api(`gamekit-projects/${enc(project_id)}/ability-vfx`, body, 'POST', extra?.signal);
|
|
371
371
|
});
|
|
372
|
-
tool('gripforge_moba_map', 'Generate, check and set the map of a MOBA project', 'The map of a moba project as a gameplay plan: bounds, team bases and spawns, 1 to 5 lanes (the minions\' paths), structure slots (towers, inhibitors, nexus), jungle zones and camp slots, objective zones, the divider (river: walkable; chasm, lava, void: crossed only at bridges), crossings, bush zones, walls, camera limits and symmetry. action "generate" builds a fair map (mirrored through the centre) from lanes, divider, size, team_size, camps_per_jungle, objectives, bushes, walls and returns it with a playability report; apply: true puts it in the project (moba_maps, the match\'s map and team size, the camera turn). action "validate" checks a map you pass, or the project\'s: every lane open end to end, spawns, structures and camps reachable, nothing out of bounds, the declared symmetry respected. action "set" stores an edited map document (refused while it has errors, unless force). action "get" returns the current map and its report. The scenery kit dresses walls and bushes with the bound models. 0 credits.', {
|
|
372
|
+
tool('gripforge_moba_map', 'Generate, dress, check and set the map of a MOBA project', 'The map of a moba project as a gameplay plan: bounds, team bases and spawns, 1 to 5 lanes (the minions\' paths), structure slots (towers, inhibitors, nexus), jungle zones and camp slots, objective zones, the divider (river: walkable; chasm, lava, void: crossed only at bridges), crossings, bush zones, walls, camera limits and symmetry. action "generate" builds a fair map (mirrored through the centre) from lanes, divider, size, team_size, camps_per_jungle, objectives, bushes, walls and returns it with a playability report; apply: true puts it in the project (moba_maps, the match\'s map and team size, the camera turn). action "validate" checks a map you pass, or the project\'s: every lane open end to end, spawns, structures and camps reachable, nothing out of bounds, the declared symmetry respected. action "set" stores an edited map document (refused while it has errors, unless force). action "get" returns the current map and its report. New maps default to style {theme: "sanctum", detail: "high", seed: 11, relief: "landscape"}. action "dress" previews a reusable procedural finish for the current map: jungle plateaus, river banks and paved fords, terraced bases, walkable staircases, carved structures, clustered forests, stone guardians and lilies; apply: true saves only the map document, keeping its lane layout and camera configuration; relief updates the shared terrain mesh, ground queries and stair collisions; landscape is the default, terraced keeps base terraces only, flat keeps a level arena. Themes: sanctum, wildwood, ashen; detail: low, medium, high; seed makes the finish repeatable. Bound Library models and textures take precedence. The scenery kit dresses walls and bushes with the bound models. 0 credits.', {
|
|
373
373
|
project_id: projectId,
|
|
374
|
-
action: schema.enum(['get', 'generate', 'validate', 'set']).optional().describe('Default: get.'),
|
|
374
|
+
action: schema.enum(['get', 'generate', 'dress', 'validate', 'set']).optional().describe('Default: get. dress previews a finish for the existing map, without rebuilding its layout; apply: true saves it.'),
|
|
375
|
+
style: schema.object({
|
|
376
|
+
relief: schema.enum(['landscape', 'terraced', 'flat']).optional().describe('Default landscape: jungle plateaus, river banks and terraced gardens; shared terrain mesh, physics and unit heights. terraced keeps only the gardens; flat keeps a level map.'),
|
|
377
|
+
theme: schema.enum(['sanctum', 'wildwood', 'ashen']).optional().describe('sanctum: pale carved stone, gold, contrasting groves and lilies; wildwood: mossy forest; ashen: weathered volcanic stone. Default: sanctum.'),
|
|
378
|
+
detail: schema.enum(['low', 'medium', 'high']).optional().describe('Procedural decoration budget, default high. Navigation is identical at all levels.'),
|
|
379
|
+
seed: schema.number().int().min(0).max(2147483646).optional().describe('Repeatable finish, default 11.'),
|
|
380
|
+
}).strict().optional().describe('generate / dress: art direction stored with the map. Paved lanes, inset seals, bevelled stone borders, banks, plants, lilies and entrance lights. Bound Library models and textures take precedence.'),
|
|
375
381
|
lanes: schema.number().int().min(1).max(5).optional().describe('generate: number of lanes (default 3).'),
|
|
376
382
|
divider: schema.enum(['river', 'chasm', 'lava', 'void', 'none']).optional().describe('generate: what separates the halves (default river).'),
|
|
377
383
|
size: schema.number().min(100).max(400).optional().describe('generate: side of the square map in metres.'),
|
|
@@ -382,7 +388,7 @@ export function registerGameKitTools(register, options, schema = z) {
|
|
|
382
388
|
walls: schema.boolean().optional().describe('generate: rock walls behind the camps (default true).'),
|
|
383
389
|
id: schema.string().max(41).optional().describe('generate: id of the new map (lowercase, digits, _).'),
|
|
384
390
|
name: schema.string().max(80).optional().describe('generate: display name of the map.'),
|
|
385
|
-
apply: schema.boolean().optional().describe('generate: store the
|
|
391
|
+
apply: schema.boolean().optional().describe('generate / dress: store the result in the project (default: preview only).'),
|
|
386
392
|
map: schema.record(schema.string(), schema.unknown()).optional().describe('validate / set: a full map document.'),
|
|
387
393
|
force: schema.boolean().optional().describe('set: store a map that has errors.'),
|
|
388
394
|
}, { readOnly: false }, async (args, extra) => {
|
package/dist/look.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Le rendu (« look ») des outils MCP : un paramètre à part du style de jeu. Même vocabulaire que
|
|
3
|
+
* `LOOKS` de `@gripforge/core` (look.ts) — `qa/look-check.mjs` vérifie que les deux listes concordent.
|
|
4
|
+
*/
|
|
5
|
+
export const LOOK_VALUES = ['stylized', 'painted', 'toon', 'anime', 'realistic', 'lowpoly', 'pixel'];
|
|
6
|
+
export const LOOK_DESCRIPTION = 'Rendering look, separate from the game style: stylized (neutral) | painted | toon | anime | realistic | lowpoly | pixel. Old values (handpainted, cartoon, low_poly…) are read as aliases. Omit for the style default.';
|
package/dist/scene-tools.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod/v4';
|
|
2
|
+
import { LOOK_DESCRIPTION, LOOK_VALUES } from './look.js';
|
|
2
3
|
/** UI and agents call the same scene commands, access checks and revision store. */
|
|
3
4
|
export function registerSceneTools(register, options, schema = z) {
|
|
4
5
|
const identifier = schema.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,159}$/);
|
|
@@ -6,7 +7,8 @@ export function registerSceneTools(register, options, schema = z) {
|
|
|
6
7
|
const revision = schema.number().int().positive();
|
|
7
8
|
const imageRef = schema.object({ assetId: identifier, revisionId: identifier, fileRole: identifier.optional() });
|
|
8
9
|
const mapWizard = schema.object({
|
|
9
|
-
style: schema.enum(['realistic', 'stylized', 'lowpoly', 'handpainted']).optional(),
|
|
10
|
+
style: schema.enum(['realistic', 'stylized', 'lowpoly', 'handpainted']).optional().describe('Legacy wording kept in the intent; it does not change the rendering. Use look.'),
|
|
11
|
+
look: schema.enum(LOOK_VALUES).optional().describe(`${LOOK_DESCRIPTION} Map: the theme gives the biome, the look the materials (LevelSpec.look).`),
|
|
10
12
|
world: schema.enum(['open_world', 'closed_arena', 'dungeon', 'linear', 'battle_map']).optional(),
|
|
11
13
|
terrain: schema.enum(['auto', 'plains', 'desert_canyon', 'forest', 'snow', 'island', 'volcanic']).optional(),
|
|
12
14
|
size: schema.enum(['small', 'medium', 'large']).optional(),
|
|
@@ -83,7 +85,7 @@ export function registerSceneTools(register, options, schema = z) {
|
|
|
83
85
|
}
|
|
84
86
|
const tool = (name, title, description, inputSchema, readOnly, callback) => register(name, { title, description, inputSchema: { ...inputSchema, ...workspace }, annotations: { readOnlyHint: readOnly, destructiveHint: false, idempotentHint: readOnly, openWorldHint: false } }, callback);
|
|
85
87
|
tool('gripforge_vehicle_doors', 'Open or close vehicle doors in the Studio', 'Save an independent door pose on one prepared vehicle instance in a saved scene. doors maps Door_FL/FR/RL/RR to 0 (closed)..1 (open). Unspecified doors retain their state; null clears the manual pose. Requires exact expectedRevision and named hinges/portable clips from prepare_vehicle. Pauses the global animation for manual control. Preserves the source asset, transforms, other instances and validated versions. Returns the actual Studio link. No generation charge.', { id: identifier, expectedRevision: revision, nodeId: identifier, doors: schema.record(schema.string().regex(/^Door_[FR][LR]$/), schema.number().min(0).max(1)).nullable() }, false, (args, extra) => call(`scenes/${args.id}/vehicle-doors`, args, 'POST', extra?.signal));
|
|
86
|
-
tool('gripforge_prepare_vehicle', 'Prepare segmented vehicle hinges in Blender', 'Durable Blender preparation of an owned static segmented GLB. Map each Door_FL/FR/RL/RR or Wheel_FL/FR/RL/RR to exact mesh node names from the segmentation report. Keeps geometry placement, UVs and PBR; normalizes +Z forward/metres using yaw and length; adds hinge origins, seat/entry sockets and portable GLB open/close/preview clips. Optional hinge coordinates are in normalized vehicle metres. thickness adds an inward door shell, not a full cabin. Optional cuts provide strictly convex door outlines [forwardZ,heightY] and depth [near,far] measured from the centre on that side, in normalized metres. Blender cuts those explicit profiles while interpolating UVs. Does not infer outlines or guarantee clearance. Returns job_id, new private work GLB, Blender source and animated Studio review link. 0 credits; storage applies. Inspect before replacement.', { source: imageRef, parts: schema.record(schema.string(), schema.array(schema.string().min(1).max(160)).min(1).max(64)).optional(), cuts: schema.array(schema.object({ role: schema.enum(['Door_FL', 'Door_FR', 'Door_RL', 'Door_RR']), outline: schema.array(schema.tuple([schema.number(), schema.number()])).min(3).max(12), depth: schema.tuple([schema.number(), schema.number()]) })).max(4).optional(), yaw: schema.number().min(-180).max(180).optional(), length: schema.number().min(2.8).max(7).optional(), openAngle: schema.number().min(20).max(85).optional(), thickness: schema.number().min(0).max(.04).optional(), hinges: schema.record(schema.string(), schema.tuple([schema.number(), schema.number(), schema.number()])).optional(), idempotency_key: schema.string().min(8).max(160).optional() }, false, (args, extra) => call('vehicles/prepare', args, 'POST', extra?.signal));
|
|
88
|
+
tool('gripforge_prepare_vehicle', 'Prepare segmented vehicle hinges in Blender', 'Durable Blender preparation of an owned static segmented GLB. Map each Door_FL/FR/RL/RR or Wheel_FL/FR/RL/RR to exact mesh node names from the segmentation report. Keeps geometry placement, UVs and PBR; normalizes +Z forward/metres using yaw and length; adds hinge origins, seat/entry sockets and portable GLB open/close/preview clips. Optional hinge coordinates are in normalized vehicle metres. thickness adds an inward door shell, not a full cabin. Optional cuts provide strictly convex door outlines [forwardZ,heightY] and depth [near,far] measured from the centre on that side, in normalized metres. cuts[].meshes restricts each cut to exact exterior/glazing meshes so seats and dashboard cannot be cut. removeMeshes discards explicitly named obsolete trim in the work copy only. Optional clearanceDepthM clears scoped inner walls behind the door skin; trim adds measured body frames, seals and moulded cards below cardTopY. framePoints follows outline in normalized [x,y,z]. Optional trim.fitToSurface projects frames, seals and the inner card onto only the explicitly selected opaque door skin, preserving the supplied outline. attachments adds scoped [min,max] [x,y,z] volumes for mirrors extending beyond the door profile. Blender cuts those explicit profiles while interpolating UVs. Does not infer outlines or guarantee clearance. Returns job_id, new private work GLB, Blender source and animated Studio review link. 0 credits; storage applies. Inspect before replacement.', { source: imageRef, parts: schema.record(schema.string(), schema.array(schema.string().min(1).max(160)).min(1).max(64)).optional(), cuts: schema.array(schema.object({ role: schema.enum(['Door_FL', 'Door_FR', 'Door_RL', 'Door_RR']), outline: schema.array(schema.tuple([schema.number(), schema.number()])).min(3).max(12), depth: schema.tuple([schema.number(), schema.number()]), meshes: schema.array(schema.string().min(1).max(160)).min(1).max(128).optional(), clearanceDepthM: schema.number().min(0).max(3).optional(), attachments: schema.array(schema.object({ bounds: schema.tuple([schema.tuple([schema.number(), schema.number(), schema.number()]), schema.tuple([schema.number(), schema.number(), schema.number()])]), meshes: schema.array(schema.string().min(1).max(160)).min(1).max(128) })).max(4).optional(), trim: schema.object({ framePoints: schema.array(schema.tuple([schema.number(), schema.number(), schema.number()])).min(3).max(12), frameRadiusM: schema.number().min(.003).max(.04).optional(), sealRadiusM: schema.number().min(.002).max(.02).optional(), cardTopY: schema.number().optional(), cardInsetM: schema.number().min(.01).max(.12).optional(), fitToSurface: schema.boolean().optional(), cardColor: schema.tuple([schema.number().min(0).max(1), schema.number().min(0).max(1), schema.number().min(0).max(1)]).optional() }).optional() })).max(4).optional(), removeMeshes: schema.array(schema.string().min(1).max(160)).max(128).optional(), yaw: schema.number().min(-180).max(180).optional(), length: schema.number().min(2.8).max(7).optional(), openAngle: schema.number().min(20).max(85).optional(), thickness: schema.number().min(0).max(.04).optional(), hinges: schema.record(schema.string(), schema.tuple([schema.number(), schema.number(), schema.number()])).optional(), idempotency_key: schema.string().min(8).max(160).optional() }, false, (args, extra) => call('vehicles/prepare', args, 'POST', extra?.signal));
|
|
87
89
|
tool('gripforge_vehicle_schema', 'Modular vehicle manufacturing contract', 'Read the concept-first vehicle recipe, canonical parts, hinge convention, limits, quotes and revision workflow.', {}, true, (args, extra) => call('vehicles', args, 'GET', extra?.signal));
|
|
88
90
|
tool('gripforge_generate_vehicle', 'Generate a vehicle from reviewed multi-view references', 'Read vehicle_schema first. Recommended recipe.workflow=body_wheels follows concept → references (five wheel-less body views + wheel) → geometry (stop/review) → topology (stop/review) → build (PBR + wheel + rig). Each geometry review uses scene_review with the returned assetRef and must be approved before the next paid step. Rigging preserves UVs and corner normals, GLB/FBX/Blend and measured Chaos setup. Top view is review-only. parts.body.geometry accepts unfinished Hunyuan/Blender GLB; parts.body.topology pins the reviewed reduced GLB; parts.<role>.source reuses finished meshes. Legacy modular_doors remains available for articulated doors. stage=plan quotes each stage without spending; paid stages require its budget. Durable job; poll generation_read. Work draft, real-render review and target-engine driving test required; no automatic publication/replacement.', { stage: schema.enum(['plan', 'concept', 'references', 'geometry', 'topology', 'build']).default('plan'), recipe: record, budget: schema.object({ usd: schema.number().nonnegative().optional(), credits: schema.number().int().nonnegative().optional() }).optional(), idempotency_key: schema.string().min(8).max(160).optional() }, false, (args, extra) => call('vehicles', args, 'POST', extra?.signal));
|
|
89
91
|
tool('gripforge_vehicle_segment', 'Separate an existing vehicle with Tripo', 'Quote then queue one durable Tripo v2 semantic segmentation of an owned immutable GLB, including Meshy or Blender sources. stage=plan is free; stage=build requires quoted budget. 40 Tripo provider credits, distinct from the GripForge quote. Returns job_id; inspect parts and texture preservation in generation_read. No automatic completion, retexture, articulation, publication or replacement. A segmented output still needs hinge preparation.', { stage: schema.enum(['plan', 'build']).default('plan'), source: imageRef, granularity: schema.enum(['simple', 'balanced', 'detailed']).optional(), budget: schema.object({ usd: schema.number().nonnegative().optional(), credits: schema.number().int().nonnegative().optional() }).optional(), idempotency_key: schema.string().min(8).max(160).optional() }, false, (args, extra) => call('vehicles/segment', args, 'POST', extra?.signal));
|
|
@@ -104,7 +106,7 @@ export function registerSceneTools(register, options, schema = z) {
|
|
|
104
106
|
tool('gripforge_armor_repair', 'Repair unfinished armor pieces', 'Fork a failed/cancelled armor job into a new work kit. Preserve completed pieces and the exact shared concept; recreate only unfinished types by default. Optional pieces chooses types explicitly. Use prompt for targeted corrections. Original checkpoints and artifacts remain intact. Costs 11 credits per regenerated canonical type (plus a concept credit only if none survived). Use generation_retry instead for a transient provider or worker error to reuse the exact same steps without another charge.', { source_job: identifier, prompt: schema.string().min(3).max(2000).optional(), pieces: schema.array(schema.enum(['helmet', 'chest', 'belt', 'pauldron', 'bracer', 'glove', 'thigh', 'greave', 'boot', 'upperarm', 'gorget', 'tasset', 'undersuit'])).min(1).max(13).optional(), idempotency_key: schema.string().min(8).max(160).optional() }, false, (args, extra) => call('armor-kits', args, 'POST', extra?.signal));
|
|
105
107
|
tool('gripforge_map_import', 'Migrate a saved terrain map', 'Queue a one-time materialization of an owned legacy terrain map into the shared Scene Engine. Preserve the saved spec, seed, resolution, family replacements, placed assets and removed props. Completed jobs return a canonical Studio link; source edits during migration cause a conflict rather than data loss.', { id: identifier }, false, (args, extra) => call('generation-jobs/map/import', { ...args, legacyMapId: args.id }, 'POST', extra?.signal));
|
|
106
108
|
tool('gripforge_map_schema', 'Map concept and generation workflow', 'Read the concept-first map workflow, immutable image reference format, costs and an example. Concepts, map work scenes and external engine delivery are separate steps.', {}, true, (args, extra) => call('generation-jobs/map', args, 'GET', extra?.signal));
|
|
107
|
-
tool('gripforge_map_generate', 'Create map concept views or an editable map', 'Queue a persistent map job. stage=concept creates a private master overview plus optional views and stops. views: overview,top_down,entrance,objective. All additional views are conditioned on the SAME pinned master; they are illustrations, not exact 3D renders. reference is an environment screenshot for a NEW layout. To add views to an existing concept, pass source_concept instead; the master is kept unchanged and is free. One credit per new image (full new 4-view set = 4); default is overview only. Poll generation_read with include_preview=true to see the views. After the user selects the concept, submit stage=build with the returned concept and a new idempotency key. Build costs 1 credit and is the default for existing callers; legacy concept_item accepts a Library image ID, while concept pins its exact version. wizard: style realistic|stylized|lowpoly|handpainted; world open_world|closed_arena|dungeon|linear|battle_map; terrain auto|plains|desert_canyon|forest|snow|island|volcanic; size small|medium|large; boundary fixed|infinite (open edge, finite terrain); spawn single|teams; teams 2|4; boss,safeZone,extraction booleans. sources selects catalog/workspace/community props. Build uses the common Scene Engine with independent terrain, regions, paths, zones and asset instances. Cancel/retry keeps successful images and billing. No automatic visual validation, promotion or Unreal import.', {
|
|
109
|
+
tool('gripforge_map_generate', 'Create map concept views or an editable map', 'Queue a persistent map job. stage=concept creates a private master overview plus optional views and stops. views: overview,top_down,entrance,objective. All additional views are conditioned on the SAME pinned master; they are illustrations, not exact 3D renders. reference is an environment screenshot for a NEW layout. To add views to an existing concept, pass source_concept instead; the master is kept unchanged and is free. One credit per new image (full new 4-view set = 4); default is overview only. Poll generation_read with include_preview=true to see the views. After the user selects the concept, submit stage=build with the returned concept and a new idempotency key. Build costs 1 credit and is the default for existing callers; legacy concept_item accepts a Library image ID, while concept pins its exact version. wizard: look stylized|painted|toon|anime|realistic|lowpoly|pixel (rendering, separate from style; omit for the theme default); style realistic|stylized|lowpoly|handpainted (legacy wording); world open_world|closed_arena|dungeon|linear|battle_map; terrain auto|plains|desert_canyon|forest|snow|island|volcanic; size small|medium|large; boundary fixed|infinite (open edge, finite terrain); spawn single|teams; teams 2|4; boss,safeZone,extraction booleans. sources selects catalog/workspace/community props. Build uses the common Scene Engine with independent terrain, regions, paths, zones and asset instances. Cancel/retry keeps successful images and billing. No automatic visual validation, promotion or Unreal import.', {
|
|
108
110
|
prompt: schema.string().min(3).max(4000), wizard: mapWizard, stage: schema.enum(['concept', 'build']).optional(),
|
|
109
111
|
reference: imageRef.optional().describe('Pinned source-environment screenshot for stage=concept only. Upload to Library and pin first.'),
|
|
110
112
|
source_concept: imageRef.optional().describe('Existing immutable master to add views to, stage=concept only. Mutually exclusive with reference; never regenerate this master.'),
|
package/dist/server.js
CHANGED
|
@@ -16,7 +16,6 @@ import { registerVfxProjectTools } from './vfx-project-tools.js';
|
|
|
16
16
|
import { registerSceneTools } from './scene-tools.js';
|
|
17
17
|
import { registerServerTools } from './server-tools.js';
|
|
18
18
|
import { registerGameKitTools } from './gamekit-tools.js';
|
|
19
|
-
import { registerStudioToolsTools } from './studio-tools-tools.js';
|
|
20
19
|
import { registerJoystickTools } from './joystick-tools.js';
|
|
21
20
|
import { registerAbilityTools } from './ability-tools.js';
|
|
22
21
|
import { registerAssetProductionTools } from './asset-production-tools.js';
|
|
@@ -35,14 +34,13 @@ import { registerMapUnrealImportLocalTools } from './map-unreal-import-local.js'
|
|
|
35
34
|
import { registerMapUnrealExportLocalTools } from './map-unreal-export-local.js';
|
|
36
35
|
const API_URL = process.env.GRIPFORGE_API_URL ?? 'https://gripforge.ai';
|
|
37
36
|
const API_KEY = process.env.GRIPFORGE_API_KEY;
|
|
38
|
-
const MCP_SELF = '0.1.
|
|
37
|
+
const MCP_SELF = '0.1.9';
|
|
39
38
|
const SUPPORTED = ['.glb', '.gltf', '.fbx', '.obj'];
|
|
40
39
|
const server = new McpServer({ name: 'gripforge', version: MCP_SELF });
|
|
41
40
|
registerVfxProjectTools(server.registerTool.bind(server), { apiUrl: API_URL, getApiKey: () => API_KEY });
|
|
42
41
|
registerSceneTools(server.registerTool.bind(server), { apiUrl: API_URL, getApiKey: () => API_KEY });
|
|
43
42
|
registerServerTools(server.registerTool.bind(server), { apiUrl: API_URL, getApiKey: () => API_KEY });
|
|
44
43
|
registerGameKitTools(server.registerTool.bind(server), { apiUrl: API_URL, getApiKey: () => API_KEY });
|
|
45
|
-
registerStudioToolsTools(server.registerTool.bind(server), { apiUrl: API_URL, getApiKey: () => API_KEY });
|
|
46
44
|
registerGameKitLocalTools(server.registerTool.bind(server), { apiUrl: API_URL, getApiKey: () => API_KEY, userAgent: `gripforge-mcp/${MCP_SELF}` });
|
|
47
45
|
registerMapUnrealLocalTools(server.registerTool.bind(server));
|
|
48
46
|
registerMapUnrealImportLocalTools(server.registerTool.bind(server));
|
|
@@ -366,7 +364,7 @@ async function downloadTo(url, dest) {
|
|
|
366
364
|
await writeFile(dest, Buffer.from(await res.arrayBuffer()));
|
|
367
365
|
return true;
|
|
368
366
|
}
|
|
369
|
-
const KIND = z.enum(['character', 'fps-arms', 'enemy', 'weapon', '
|
|
367
|
+
const KIND = z.enum(['character', 'fps-arms', 'enemy', 'weapon', 'prop', 'texture', 'skybox', 'loading', 'hud', 'button', 'bind', 'vfx', 'animation', 'audio']);
|
|
370
368
|
const TEX_EXTS = ['.png', '.jpg', '.jpeg', '.webp'];
|
|
371
369
|
server.tool('gripforge_texture_prep', 'Prep a game texture (terrain / props): optional 50% wrap + seam blend, then a faithful ' +
|
|
372
370
|
'lanczos upscale (1× / 2× / 4×, capped at 1024 or 2048). The pattern is PRESERVED — ' +
|
|
@@ -444,7 +442,7 @@ server.tool('gripforge_texture_prep', 'Prep a game texture (terrain / props): op
|
|
|
444
442
|
],
|
|
445
443
|
};
|
|
446
444
|
});
|
|
447
|
-
server.tool('gripforge_style_kit', 'Resolve "Devil May Cry like" / "
|
|
445
|
+
server.tool('gripforge_style_kit', 'Resolve "Devil May Cry like" / "genshin" to the locker kit already tagged with that game look (characters, enemies, weapons, props). Call this BEFORE generating. Reuse the returned ids.', { prompt: z.string().min(2).max(240).describe('e.g. "devil may cry like", "un ennemi genshin"') }, async ({ prompt }) => {
|
|
448
446
|
if (!API_KEY)
|
|
449
447
|
return err('GRIPFORGE_API_KEY missing.');
|
|
450
448
|
const qs = new URLSearchParams({ limit: '200' }); // style= does the matching; q= would also require the prompt in the NAME and empty the kit;
|
|
@@ -876,24 +874,18 @@ server.tool('gripforge_concept_correct', 'Turn a character concept image into a
|
|
|
876
874
|
return err(String(data.error ?? res.status));
|
|
877
875
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
878
876
|
});
|
|
879
|
-
server.tool('gripforge_library_list', 'List the GripForge Library locker (characters, enemies, weapons, props, textures, HUD, buttons, binds). Filter by kind and/or game style. Every item carries `rank` {score/100, grade A–D, reasons}: fitness for a GripForge game; sort=rank lists the best fit first.
|
|
877
|
+
server.tool('gripforge_library_list', 'List the GripForge Library locker (characters, enemies, weapons, props, textures, HUD, buttons, binds). Filter by kind and/or game style. Every item carries `rank` {score/100, grade A–D, reasons}: fitness for a GripForge game; sort=rank lists the best fit first.', {
|
|
880
878
|
kind: KIND.optional().describe('Filter by kind'),
|
|
881
879
|
q: z.string().optional().describe('Search name/filename'),
|
|
882
|
-
|
|
883
|
-
roles: z.boolean().optional().describe('With kind: also match assets where this kind is a secondary role'),
|
|
884
|
-
style: z.string().optional().describe('Game look (devil-may-cry, dmc, open-world-adventure)'),
|
|
880
|
+
style: z.string().optional().describe('Game look (devil-may-cry, dmc, genshin)'),
|
|
885
881
|
sort: z.enum(['newest', 'rank']).optional().describe('newest (default) or rank: best fit first'),
|
|
886
882
|
target: z.enum(['mobile', 'desktop']).optional().describe('Platform for the rank budgets. Defaults to mobile.'),
|
|
887
|
-
}, async ({ kind, q,
|
|
883
|
+
}, async ({ kind, q, style, sort, target }) => {
|
|
888
884
|
if (!API_KEY)
|
|
889
885
|
return err('GRIPFORGE_API_KEY missing.');
|
|
890
886
|
const qs = new URLSearchParams();
|
|
891
887
|
if (kind)
|
|
892
888
|
qs.set('kind', kind);
|
|
893
|
-
if (subtype)
|
|
894
|
-
qs.set('subtype', subtype);
|
|
895
|
-
if (roles)
|
|
896
|
-
qs.set('roles', '1');
|
|
897
889
|
if (q)
|
|
898
890
|
qs.set('q', q);
|
|
899
891
|
if (style)
|
|
@@ -1039,26 +1031,21 @@ server.tool('gripforge_library_pull', 'Download a Library item into the open rep
|
|
|
1039
1031
|
],
|
|
1040
1032
|
};
|
|
1041
1033
|
});
|
|
1042
|
-
server.tool('gripforge_library_tag', '
|
|
1034
|
+
server.tool('gripforge_library_tag', 'Add a game look to tags so style_kit can find the item. Does not overwrite grip style melee/gun.', {
|
|
1043
1035
|
id: z.string().describe('Library id (lib_…)'),
|
|
1044
1036
|
style: z.string().optional().describe('Game look id or alias (devil-may-cry, dmc)'),
|
|
1045
1037
|
tags: z.array(z.string()).optional().describe('Replace tags. Omit to keep existing and just add style.'),
|
|
1046
|
-
|
|
1047
|
-
subtypes: z.array(z.string()).optional().describe('Requalify: replace the cumulative subtypes ([] clears them)'),
|
|
1048
|
-
also_kinds: z.array(z.string()).optional().describe('Requalify: replace the secondary roles ([] clears them)'),
|
|
1049
|
-
}, async ({ id, style, tags, kind, subtypes, also_kinds }) => {
|
|
1038
|
+
}, async ({ id, style, tags }) => {
|
|
1050
1039
|
if (!API_KEY)
|
|
1051
1040
|
return err('GRIPFORGE_API_KEY missing.');
|
|
1052
1041
|
const res = await fetch(`${API_URL}/api/v1/library/${encodeURIComponent(id)}`, {
|
|
1053
1042
|
method: 'PATCH',
|
|
1054
1043
|
headers: { ...apiHeaders(), 'content-type': 'application/json' },
|
|
1055
|
-
body: JSON.stringify({ style, tags
|
|
1044
|
+
body: JSON.stringify({ style, tags }),
|
|
1056
1045
|
});
|
|
1057
1046
|
const data = await res.json().catch(() => ({}));
|
|
1058
|
-
if (!res.ok)
|
|
1059
|
-
|
|
1060
|
-
return err(fields ? Object.entries(fields).map(([f, m]) => `${f}: ${m.join('; ')}`).join(' | ') : String(data.error ?? res.status));
|
|
1061
|
-
}
|
|
1047
|
+
if (!res.ok)
|
|
1048
|
+
return err(String(data.error ?? res.status));
|
|
1062
1049
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
1063
1050
|
});
|
|
1064
1051
|
server.tool('gripforge_hand_rig', 'Complete a rigged Library character with finger bones (3 phalanges × 5 fingers per hand, Mixamo names) built from its hand mesh — no provider rigs fingers, so without this a hand cannot close on a weapon and pistol/knife clips have nothing to drive. New characters get it automatically in gripforge_generate_character; use this on characters generated before. Updates the item in place. 0 credits. Then re-run gripforge_animate.', { character_id: z.string().describe('Library character / enemy / bind (lib_…)') }, async ({ character_id }) => {
|
|
@@ -1444,24 +1431,22 @@ server.tool('gripforge_vehicle_wheels', 'Split the 4 wheels out of a fused kart/
|
|
|
1444
1431
|
return err(String(data.error ?? res.status));
|
|
1445
1432
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
1446
1433
|
});
|
|
1447
|
-
server.tool('gripforge_vehicle_finish', 'Repair generated automotive surfaces. Preferred: source (immutable static GLB with Body/Wheel_* groups) + repair recipe queues a persistent Blender job; guided glazing/panel regions, paint cleanup, coloured caliper extraction, measured replacement panels/curves with PBR and fitted boundaries, optional rebuilt wheels and rigid wheel rig. features.marks adds surface-fitted pinned PNG logos, relief badges and mesh lettering; wheel markings follow their wheel, named authored marks are replaced in the new work copy. For grilles or trim on a reconstructed recess, set curves.fitSurface.panel to its non-structural panel name to follow the final part. Returns a NEW private work version with GLB/Blend/optional FBX and GripForge review link. Does not promote or overwrite the source. Regions use +Y up/+Z forward metres, bounded displacement; region-only glass stays opaque, authored transmissive panes need an authored cabin.
|
|
1434
|
+
server.tool('gripforge_vehicle_finish', 'Repair generated automotive surfaces. Preferred: source (immutable static GLB with Body/Wheel_* groups) + repair recipe queues a persistent Blender job; guided glazing/panel regions, paint cleanup, coloured caliper extraction, measured replacement panels/curves with PBR and fitted boundaries, optional rebuilt wheels and rigid wheel rig. features.marks adds surface-fitted pinned PNG logos, relief badges and mesh lettering; wheel markings follow their wheel, named authored marks are replaced in the new work copy. For grilles or trim on a reconstructed recess, set curves.fitSurface.panel to its non-structural panel name to follow the final part. Returns a NEW private work version with GLB/Blend/optional FBX and GripForge review link. Does not promote or overwrite the source. Regions use +Y up/+Z forward metres, bounded displacement; region-only glass stays opaque, authored transmissive panes need an authored cabin. Poll/cancel/retry generation-jobs. 0 provider credits. Legacy id without repair uses one-time material/wheel finish and rewrites its Library file.', {
|
|
1448
1435
|
id: z.string().optional().describe('Library id; with repair pins source, without repair uses legacy finish'),
|
|
1449
1436
|
source: z.object({ assetId: z.string(), revisionId: z.string(), fileRole: z.string().optional() }).optional().describe('Pinned static source, instead of id'),
|
|
1450
1437
|
repair: z.object({
|
|
1451
1438
|
regions: z.array(z.object({ name: z.string(), bounds: z.tuple([z.tuple([z.number(), z.number(), z.number()]), z.tuple([z.number(), z.number(), z.number()])]), select: z.enum(['neutral', 'paint', 'all']), surface: z.enum(['glass', 'paint']), fitAxis: z.enum(['x', 'y', 'z']), maxOffsetM: z.number().min(0).max(.05).optional() })).max(16),
|
|
1452
1439
|
denoise: z.object({ iterations: z.number().int().min(1).max(80).optional(), maxOffsetM: z.number().min(0).max(.05).optional(), normalIterations: z.number().int().min(0).max(30).optional() }).optional().describe('Bounded Body smoothing across UV seams; pins open boundaries, retains UVs and wheel/caliper transforms; review small details'),
|
|
1453
1440
|
cleanPaint: z.boolean().optional(), paintColor: z.tuple([z.number(), z.number(), z.number()]).optional(),
|
|
1454
|
-
cabin: z.object({ bounds: z.tuple([z.tuple([z.number(), z.number(), z.number()]), z.tuple([z.number(), z.number(), z.number()])]), seats: z.union([z.literal(2), z.literal(4)]).optional(), steeringSide: z.enum(['left', 'right']).optional(), glassMeshes: z.array(z.string().min(1).max(120)).max(12).optional(), glassTransmission: z.number().min(0).max(1).optional(), glassThicknessM: z.number().min(.001).max(.02).optional(), upholsteryColor: z.tuple([z.number(), z.number(), z.number()]).optional(), glassColor: z.tuple([z.number(), z.number(), z.number()]).optional() }).optional().describe('Measured parametric cabin: seats, dashboard, steering, console, floor and trim. Exact isolated transmissive Body pane names only; no inferred segmentation.'),
|
|
1455
1441
|
calipers: z.object({ color: z.tuple([z.number(), z.number(), z.number()]), tolerance: z.number().min(.05).max(.4).optional() }).optional(),
|
|
1456
1442
|
features: z.object({
|
|
1457
1443
|
replaceBody: z.boolean().optional().describe('Explicitly replace coachwork in a new work copy using structural panels; preserve wheels and Body calipers.'),
|
|
1458
|
-
shellDenoise: z.object({ iterations: z.number().int().min(1).max(80).optional(), normalIterations: z.number().int().min(0).max(30).optional(), maxOffsetM: z.number().min(0).max(.05).optional() }).optional().describe('Refine only a newly replaced structural shell; preserves window/cut boundaries and never smooths badges, lamps or wheels.'),
|
|
1459
1444
|
panels: z.array(z.object({
|
|
1460
1445
|
name: z.string(), points: z.array(z.array(z.tuple([z.number(), z.number(), z.number()])).min(2).max(12)).min(2).max(12),
|
|
1461
1446
|
structural: z.boolean().optional().describe('New coachwork shell, required with replaceBody; cannot fit or clip the discarded source.'),
|
|
1462
1447
|
normal: z.tuple([z.number(), z.number(), z.number()]).optional(),
|
|
1463
1448
|
segmentsU: z.number().int().min(4).max(96).optional(), segmentsV: z.number().int().min(4).max(96).optional(),
|
|
1464
|
-
mirrorX: z.boolean().optional(), material: z.object({ color: z.tuple([z.number(), z.number(), z.number()]).optional(), metallic: z.number().min(0).max(1).optional(), roughness: z.number().min(.02).max(1).optional(), coat: z.number().min(0).max(1).optional(), transmission: z.number().min(0).max(1).optional(), ior: z.number().min(1).max(2.5).optional(), emission: z.number().min(0).max(10).optional()
|
|
1449
|
+
mirrorX: z.boolean().optional(), material: z.object({ color: z.tuple([z.number(), z.number(), z.number()]).optional(), metallic: z.number().min(0).max(1).optional(), roughness: z.number().min(.02).max(1).optional(), coat: z.number().min(0).max(1).optional(), transmission: z.number().min(0).max(1).optional(), ior: z.number().min(1).max(2.5).optional(), emission: z.number().min(0).max(10).optional() }).optional(),
|
|
1465
1450
|
replace: z.object({ axis: z.enum(['x', 'y', 'z']), depthM: z.number().min(.001).max(.5).optional() }).optional(),
|
|
1466
1451
|
fitBoundary: z.object({ axis: z.enum(['x', 'y', 'z']), direction: z.union([z.literal(1), z.literal(-1)]).optional(), offsetM: z.number().min(0).max(.01).optional(), fullSurface: z.boolean().optional() }).optional(), sealRadiusM: z.number().min(.0005).max(.03).optional(),
|
|
1467
1452
|
})).max(32).optional(),
|
|
@@ -1469,7 +1454,7 @@ server.tool('gripforge_vehicle_finish', 'Repair generated automotive surfaces. P
|
|
|
1469
1454
|
name: z.string(), points: z.array(z.tuple([z.number(), z.number(), z.number()])).min(2).max(128),
|
|
1470
1455
|
fitSurface: z.object({ axis: z.enum(['x', 'y', 'z']), direction: z.union([z.literal(1), z.literal(-1)]).optional(), offsetM: z.number().min(0).max(.01).optional(), panel: z.string().trim().min(1).max(80).optional() }).optional(),
|
|
1471
1456
|
radiusM: z.number().min(.0005).max(.08).optional(), closed: z.boolean().optional(), mirrorX: z.boolean().optional(),
|
|
1472
|
-
material: z.object({ color: z.tuple([z.number(), z.number(), z.number()]).optional(), metallic: z.number().min(0).max(1).optional(), roughness: z.number().min(.02).max(1).optional(), coat: z.number().min(0).max(1).optional(), transmission: z.number().min(0).max(1).optional(), ior: z.number().min(1).max(2.5).optional(), emission: z.number().min(0).max(10).optional()
|
|
1457
|
+
material: z.object({ color: z.tuple([z.number(), z.number(), z.number()]).optional(), metallic: z.number().min(0).max(1).optional(), roughness: z.number().min(.02).max(1).optional(), coat: z.number().min(0).max(1).optional(), transmission: z.number().min(0).max(1).optional(), ior: z.number().min(1).max(2.5).optional(), emission: z.number().min(0).max(10).optional() }).optional(),
|
|
1473
1458
|
})).max(256).optional(),
|
|
1474
1459
|
marks: z.array(z.object({
|
|
1475
1460
|
name: z.string().min(1).max(80), kind: z.enum(['decal', 'badge', 'text']), target: z.enum(['Body', 'Wheel_FL', 'Wheel_FR', 'Wheel_RL', 'Wheel_RR']).optional(),
|
|
@@ -1477,13 +1462,12 @@ server.tool('gripforge_vehicle_finish', 'Repair generated automotive surfaces. P
|
|
|
1477
1462
|
widthM: z.number().min(.005).max(.8), heightM: z.number().min(.005).max(.8), offsetM: z.number().min(.0002).max(.01).optional(), depthM: z.number().min(0).max(.01).optional(), maxDistanceM: z.number().min(.001).max(.12).optional(), segments: z.number().int().min(4).max(32).optional(), mirrorX: z.boolean().optional(),
|
|
1478
1463
|
outline: z.array(z.tuple([z.number().min(-.5).max(.5), z.number().min(-.5).max(.5)])).min(3).max(64).optional(),
|
|
1479
1464
|
artwork: z.object({ assetId: z.string(), revisionId: z.string(), fileRole: z.string().optional() }).optional(), text: z.string().min(1).max(48).optional(),
|
|
1480
|
-
material: z.object({ color: z.tuple([z.number(), z.number(), z.number()]).optional(), metallic: z.number().min(0).max(1).optional(), roughness: z.number().min(.02).max(1).optional(), coat: z.number().min(0).max(1).optional(), transmission: z.number().min(0).max(1).optional(), ior: z.number().min(1).max(2.5).optional(), emission: z.number().min(0).max(10).optional()
|
|
1465
|
+
material: z.object({ color: z.tuple([z.number(), z.number(), z.number()]).optional(), metallic: z.number().min(0).max(1).optional(), roughness: z.number().min(.02).max(1).optional(), coat: z.number().min(0).max(1).optional(), transmission: z.number().min(0).max(1).optional(), ior: z.number().min(1).max(2.5).optional(), emission: z.number().min(0).max(10).optional() }).optional(),
|
|
1481
1466
|
})).max(32).optional().describe('Surface-fitted logos and relief: decals require pinned PNG artwork; badges add thickness and a convex outline; text makes mesh lettering. Explicit normal/up frame; target wheel marks spin with that wheel. Optional receiver scopes fitting to a mesh in the target. PNGs <=1 MiB/2048px, max 16 distinct sources. No URLs or local paths.'),
|
|
1482
1467
|
}).optional().describe('Measured panels, curves, badges and decals; explicit PBR, clipping, symmetry, 250k vertex budget.'),
|
|
1483
1468
|
rig: z.object({ wheelRadius: z.number(), wheelWidth: z.number() }).optional(),
|
|
1484
1469
|
wheelRebuild: z.object({ radiusM: z.number().min(.1).max(1.5), widthM: z.number().min(.05).max(.8), spokes: z.number().int().min(3).max(12).optional(), segments: z.number().int().min(24).max(128).optional(), trackM: z.number().min(.3).max(6).optional() }).optional().describe('Replace generated wheel meshes with measured tyres, dished spokes and brake rotors. Optional trackM adjusts axle track and matching explicit Body calipers together; omitted preserves wheel pivots.'),
|
|
1485
1470
|
}).optional().describe('Guided surface repair; never edits the source'),
|
|
1486
|
-
render_profile: z.object({ look: z.enum(['studio', 'daylight']).optional(), quality: z.enum(['performance', 'balanced', 'quality']).optional(), lengthM: z.number().min(1).max(25).optional(), ground: z.boolean().optional(), depthOfField: z.boolean().optional(), focusDistanceM: z.number().min(.1).max(1000).optional() }).optional().describe('Shared automotive review scene profile; balanced studio by default. DOF off unless explicitly requested.'),
|
|
1487
1471
|
name: z.string().optional(), idempotency_key: z.string().min(8).max(160).optional(),
|
|
1488
1472
|
smooth: z.number().int().min(0).max(60).optional().describe('body smoothing passes (default 15, 0 = none)'),
|
|
1489
1473
|
wheels: z.boolean().optional().describe('false keeps the generated wheels'),
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// mcp-handler requires v4 field schemas for raw shapes. zod 3.25 ships v4
|
|
3
3
|
// alongside v3, so existing stdio tools can retain their current v3 schemas.
|
|
4
4
|
import { z } from 'zod/v4';
|
|
5
|
+
import { LOOK_DESCRIPTION, LOOK_VALUES } from './look.js';
|
|
5
6
|
// The hosted SDK uses a newer Zod v4 serializer than the npm SDK. Inject its
|
|
6
7
|
// matching factory so schemas remain compatible with each host's serializer.
|
|
7
8
|
export function registerVfxProjectTools(register, options, schema = z) {
|
|
@@ -44,5 +45,5 @@ export function registerVfxProjectTools(register, options, schema = z) {
|
|
|
44
45
|
tool('gripforge_vfx_project_render', 'Render actual VFX frames for inspection', 'Queue a durable render of an owned id OR an unsaved project at 1–4 timestamps. Returns job_id immediately. Poll gripforge_generation_read with include_preview for the actual rendered image, coverage, clipping and shader diagnostics. Uses the shared GripForge SceneViewport. Saves preview evidence only, no project revision. Requires workspace write access because the job and preview consume storage. Render timings are not browser FPS.', { idempotency_key: schema.string().min(8).max(160).optional(), id: id.optional(), project: project.optional(), times }, false, (args, extra) => api('/render', args, 'POST', extra?.signal));
|
|
45
46
|
tool('gripforge_vfx_project_review', 'Review an existing work revision', 'Queue a fresh GripForge capture and independent visual review without writing another source revision. Poll gripforge_generation_read; inspect its preview, then explicitly promote if approved.', { id, expected_revision: schema.number().int().positive(), idempotency_key: schema.string().min(8).max(160).optional() }, false, (args, extra) => api(`/${args.id}/review`, args, 'POST', extra?.signal));
|
|
46
47
|
tool('gripforge_vfx_project_promote', 'Set the visually approved VFX version', 'Explicitly promote work after a trusted positive review matching this source, dependencies and renderer. expectedCurrent is the existing validated revision, or null.', { id, revision: schema.number().int().positive(), expectedCurrent: schema.number().int().positive().nullable() }, false, (args, extra) => api(`/${args.id}/promote`, args, 'POST', extra?.signal));
|
|
47
|
-
tool('gripforge_vfx_project_generate', 'Create, render and refine a custom VFX', 'Text/image -> durable generation job -> original geometry/shaders -> actual GripForge rendering -> visual critique/correction -> workspace work revision. Returns immediately with job_id, status_url and studio_url. Poll gripforge_generation_read, then open its source_url to inspect the saved source and review. Closing the request does not cancel the job. Completed steps survive worker restart. Pass id + expected_revision to refine; image_id must belong to this workspace. A visually rejected candidate never replaces the validated version.', { idempotency_key: schema.string().min(8).max(160).optional(), prompt: schema.string().max(2000).optional(), visual_style: schema.enum(
|
|
48
|
+
tool('gripforge_vfx_project_generate', 'Create, render and refine a custom VFX', 'Text/image -> durable generation job -> original geometry/shaders -> actual GripForge rendering -> visual critique/correction -> workspace work revision. Returns immediately with job_id, status_url and studio_url. Poll gripforge_generation_read, then open its source_url to inspect the saved source and review. Closing the request does not cancel the job. Completed steps survive worker restart. Pass id + expected_revision to refine; image_id must belong to this workspace. A visually rejected candidate never replaces the validated version.', { idempotency_key: schema.string().min(8).max(160).optional(), prompt: schema.string().max(2000).optional(), visual_style: schema.enum(LOOK_VALUES).optional().describe('Rendering look of the effect (same vocabulary as look). Default stylized.'), look: schema.enum(LOOK_VALUES).optional().describe(LOOK_DESCRIPTION), image_mode: schema.enum(['spatial', 'animated-artwork']).optional().describe('Default spatial: build a 3D effect. animated-artwork explicitly opts into a flat animated illustration.'), image_id: id.optional(), image_data: schema.string().max(12 * 1024 * 1024).optional(), id: id.optional(), expected_revision: schema.number().int().positive().optional() }, false, (args, extra) => api('/generate', args, 'POST', extra?.signal), true);
|
|
48
49
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gripforgeai/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
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",
|