@gripforgeai/mcp 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,87 @@ Turn prompts into production-ready game assets — animated characters with thei
6
6
 
7
7
  MCP client for the [GripForge](https://gripforge.ai) API — from Claude Code, Cursor, Windsurf, VS Code or any MCP client.
8
8
 
9
- ## Hosted endpoint (zero install)
9
+ ## How to use
10
+
11
+ **Hosted — nothing to install.** Point any remote-capable MCP client at `https://gripforge.ai/mcp`
12
+ with your API key ([create one here](https://gripforge.ai/account)):
13
+
14
+ ```jsonc
15
+ {
16
+ "mcpServers": {
17
+ "gripforge": {
18
+ "url": "https://gripforge.ai/mcp",
19
+ "headers": { "x-api-key": "gf_..." }
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ **Claude Code**, in one line:
26
+
27
+ ```bash
28
+ claude mcp add --transport http gripforge https://gripforge.ai/mcp --header "x-api-key: gf_..."
29
+ ```
30
+
31
+ **Local, via npm** — when the agent must write files into your repository:
32
+
33
+ ```jsonc
34
+ {
35
+ "mcpServers": {
36
+ "gripforge": {
37
+ "command": "npx",
38
+ "args": ["-y", "@gripforgeai/mcp"],
39
+ "env": { "GRIPFORGE_API_KEY": "gf_..." }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ ## Key features
46
+
47
+ - **Characters that move.** Describe one, get a rigged T-pose with an animation set — idle, run,
48
+ attack — ready to drop into a scene.
49
+ - **Weapons in hand.** GripForge finds the hand bone on any rig, scales the prop, closes the fist
50
+ around the grip, and returns the bind plus Three.js, Unity and Godot snippets.
51
+ - **Seamless textures.** Tileable material sets from a prompt or from your own image, upscaled
52
+ without invention.
53
+ - **Terrain and maps.** Playable room graphs, top-down shooter layouts with spawns and lanes,
54
+ greybox reconstruction from references.
55
+ - **VFX and HUD.** Animated effects and interface kits exported for your engine.
56
+ - **Playable game kits.** Assemble modular capabilities — movement, combat, enemies, worlds — into
57
+ a game that runs in the browser, then bind your own assets to it.
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", "open world adventure").
60
+
61
+ ## Use cases
62
+
63
+ - *"Attach this sword to my knight, right hand, then export the armed GLB."* — the agent handles the
64
+ bone, the scale and the fist; you get a file that loads.
65
+ - *"Make me a boss for an ashen underworld, with three phases and an arena."* — stats, attacks and
66
+ engine snippets come back together.
67
+ - *"Give this floor a mossy cobblestone texture that tiles."* — a seamless set, saved to the Library.
68
+ - *"Build a playable roguelike slice I can try in the browser."* — a game kit project with its
69
+ assets bound, playable from a link.
70
+
71
+ ## FAQ
72
+
73
+ **Do I need to install anything?** No. The hosted endpoint works from any MCP client that speaks
74
+ streamable HTTP. The npm package exists for one reason: letting the agent write files directly into
75
+ your repository.
76
+
77
+ **Which engines are supported?** Unity, Godot, Unreal and Three.js — assets come with the binds and
78
+ snippets each one expects.
79
+
80
+ **How is it billed?** Credits, per generation. Reading the Library, animating an existing character,
81
+ level and map kits cost nothing; generating a character or a weapon costs 10.
82
+
83
+ **Where do my assets live?** In your workspace Library. You can pull them into your repository, push
84
+ your own, and share them with the community catalog.
85
+
86
+ **Can I use my own concept art?** Yes — pass an image and GripForge builds the T-pose sheet from it
87
+ rather than inventing a character.
88
+
89
+ ## Hosted endpoint
10
90
 
11
91
  No Node required — point any remote-capable MCP client at:
12
92
 
@@ -85,19 +165,22 @@ Also works with Cursor, Windsurf and any MCP-compatible client — same
85
165
  `command` / `args` / `env` triple.
86
166
 
87
167
 
88
- ## Tools
168
+ ## Tool reference
89
169
 
90
170
  Hosted HTTP MCP (`https://gripforge.ai/mcp`) is always current. This npm package
91
171
  writes files into the repo (`out_dir`), including `gripforge_hud`,
92
172
  `gripforge_hud_bar` and `gripforge_cape`. Hosted-only: `gripforge_make_seamless`.
93
173
  Full list: https://gripforge.ai/mcp-docs
94
174
 
95
- - `gripforge_style_kit` — resolve "Devil May Cry like" / "genshin" to locker ids
175
+ - `gripforge_style_kit` — resolve "Devil May Cry like" / "open world adventure" to locker ids
96
176
  already tagged with that look. **Call this before generating.** Reuse the ids.
97
177
  - `gripforge_generate_character` — new T-pose + auto-rig into Library (10 credits).
98
178
  `kind=enemy` or the word "enemy" in the prompt. Skip this if style_kit already
99
179
  returned a character.
100
- - `gripforge_boss` — playable boss kit (stats, phases, attacks, arena, engine snippets).
180
+ - `gripforge_boss` — boss configuration (stats, phases, attacks, arena, engine integration snippets). Rigging, clips and in-engine combat integration remain separate.
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).
101
184
  0 credits. Reuses a locker enemy. `generate=true` forges a new kind=enemy (10 credits).
102
185
  Then `gripforge_animate` with the returned archetype.
103
186
  - `gripforge_concept_correct` — concept image → strict T-pose sheet (1 credit).
@@ -125,6 +208,7 @@ Full list: https://gripforge.ai/mcp-docs
125
208
  - `gripforge_scene_kit` — locker props + suggested layout for a game look.
126
209
  - `gripforge_light_kit` / `gripforge_font` / `gripforge_navmesh` — lights+fog, webfont+ranks, walkable AABB. 0 credits.
127
210
  - `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.
128
212
  Optional `third_person: true` adds portable character-facing JavaScript and a
129
213
  Three.js integration example; the existing input map is unchanged. 0 credits.
130
214
  - `gripforge_viewmodel_kit` — CS-style FPS arms + gun under Camera3D/Hold. Write viewmodel.json + gf_viewmodel.gd. 0 credits.
@@ -168,6 +252,10 @@ Full parameters: https://gripforge.ai/mcp-docs
168
252
  - `gripforge_game_capabilities` — what the project provides, what is missing for a goal and which kits fill each gap.
169
253
  - `gripforge_game_project` — `action=list|create|get|bind|data|delete` on projects (bindings slot → `lib_…`, data collections, `confirm=true` to delete).
170
254
  - `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).
171
259
 
172
260
  ## Character facing for third-person games
173
261
 
@@ -198,6 +286,22 @@ network systems. Omitting `third_person`, or setting it to `false`, returns the
198
286
  existing input kit without the additional section. The option is available in
199
287
  the hosted MCP and this source checkout; it is not in published npm `0.1.5`.
200
288
 
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
+
201
305
  ## Performance diagnostics
202
306
 
203
307
  `gripforge_performance` is included in this source checkout and the hosted MCP.
@@ -244,7 +348,174 @@ CPU/driver time already included in `cpuMs`; `gpuMs` is meaningful only when
244
348
  actually measured. This tool does not control a browser, change game settings,
245
349
  publish code or automatically fix a game.
246
350
 
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
+
247
479
  ## Env
248
480
 
249
481
  - `GRIPFORGE_API_KEY` (required) — 1 credit = 1 successful attach
250
482
  - `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.
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod/v4';
2
+ export const ABILITY_TOOL_NAMES = ['gripforge_abilities'];
3
+ export function registerAbilityTools(register, options, schema = z) {
4
+ const shape = { action: schema.enum(['schema', 'example', 'validate', 'export']), target: schema.enum(['threejs', 'godot', 'unity', 'unreal', 'all']).optional(), pack: schema.record(schema.string(), schema.unknown()).optional().describe('Versioned ability pack. Read schema/example first; author a reusable pack then validate it.'), model_asset: schema.string().regex(/^lib_[A-Za-z0-9_-]{8,64}$/).optional().describe('Library model id bound to the example. Does not rig or animate it.') };
5
+ register('gripforge_abilities', { title: 'Abilities / skills · four engines', description: 'combat.abilities: inspect schema, get an editable Chrono Crab example, validate a pack or export executable Three.js, Godot, Unity and Unreal source adapters. Shared costs, cooldowns, conditions, targeting, windup/active/recovery and presentation cues. Native adapters require the game combat backend and asset bindings. Returns relative path/content files; no rig/animation/VFX generation or automatic installation. Persist through gripforge_gamekit_configure config.pack or project data. 0 credits.', inputSchema: shape, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false } }, async (args) => {
6
+ const parsed = schema.object(shape).strict().safeParse(args);
7
+ if (!parsed.success)
8
+ return { isError: true, content: [{ type: 'text', text: parsed.error.message }] };
9
+ const key = options.getApiKey();
10
+ if (!key)
11
+ return { isError: true, content: [{ type: 'text', text: 'GripForge API key required.' }] };
12
+ try {
13
+ const response = await fetch(options.apiUrl.replace(/\/$/, '') + '/api/v1/abilities', { method: 'POST', headers: { 'content-type': 'application/json', 'x-api-key': key, 'x-gripforge-client': 'mcp' }, body: JSON.stringify(parsed.data), signal: AbortSignal.timeout(30000) });
14
+ const data = await response.json();
15
+ return { ...(!response.ok ? { isError: true } : {}), content: [{ type: 'text', text: JSON.stringify(data) }], structuredContent: data };
16
+ }
17
+ catch (e) {
18
+ return { isError: true, content: [{ type: 'text', text: e instanceof Error ? e.message : 'Ability request failed' }] };
19
+ }
20
+ });
21
+ }
@@ -0,0 +1,73 @@
1
+ import { z } from 'zod/v4';
2
+ export const ARCHITECTURE_TOOL_NAMES = ['gripforge_architecture_schema', 'gripforge_generate_building', 'gripforge_generate_district'];
3
+ /** Hosted and local MCP share the same recipe and durable server workflow. */
4
+ export function registerArchitectureTools(register, options, schema = z) {
5
+ const identifier = schema.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,159}$/);
6
+ const ref = schema.object({ assetId: identifier, revisionId: identifier, fileRole: identifier.optional() }).strict();
7
+ const building = schema.object({
8
+ id: schema.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,47}$/).optional(),
9
+ name: schema.string().min(1).max(100).optional(), prompt: schema.string().min(3).max(300),
10
+ style: schema.enum(['realistic', 'stylized', 'lowpoly', 'handpainted']).optional(),
11
+ use: schema.enum(['residential', 'retail', 'office', 'industrial', 'mixed']).optional(),
12
+ floors: schema.number().int().min(1).max(30).optional(),
13
+ 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.'),
14
+ roof: schema.enum(['flat', 'pitched']).optional(), facade: schema.string().max(120).optional(),
15
+ polycount: schema.number().int().min(4000).max(80000).optional(),
16
+ concept: ref.optional().describe('Owned PNG/JPEG/WebP revision for Meshy image-to-3D. At most 12 MiB. Mutually exclusive with source.'),
17
+ concepts: schema.array(ref).min(1).max(4).optional().describe('Coherent views of ONE building, front first, for Meshy multi-image-to-3D. Exclusive with concept/source. Review coherence before building.'),
18
+ meshQuality: schema.object({ geometry: schema.enum(['standard', '2k', '4k']).optional(), texture: schema.enum(['2k', '4k', '8k']).optional() }).strict().optional().describe('Meshy 7.1 quality. Defaults standard geometry and 2K PBR. Multi-image supports standard/2k geometry only. Texture resolution is preserved during optimization. Read the updated quote.'),
19
+ source: ref.optional().describe('Reuse an owned static GLB revision instead of paying for this building. The source is never modified.'),
20
+ }).strict();
21
+ const material = schema.record(schema.string(), schema.unknown()).describe('SceneMaterialOverride: color, roughness, metalness, map/normalMap/roughnessMap/metalnessMap as owned immutable SceneAssetRef, repeat:[u,v]. Validated by the shared Scene Engine.');
22
+ const district = schema.object({
23
+ version: schema.literal(1).optional(), name: schema.string().min(1).max(100).optional(),
24
+ buildings: schema.array(building).min(1).max(8).describe('Unique building definitions with distinct ids. Each new model is manufactured once.'),
25
+ repetitions: schema.number().int().min(1).max(4).optional().describe('Instances per definition, without additional model generation cost.'),
26
+ streetWidth: schema.number().min(6).max(30).optional(), sidewalkWidth: schema.number().min(1).max(8).optional(), gap: schema.number().min(1).max(30).optional(),
27
+ materials: schema.object({ road: material.optional(), sidewalk: material.optional() }).strict().optional(),
28
+ }).strict();
29
+ const shared = {
30
+ stage: schema.enum(['plan', 'build']).optional().describe('Default plan is free and does not generate. Read its quote, then explicitly build.'),
31
+ budget: schema.object({ credits: schema.number().nonnegative().optional(), usd: schema.number().nonnegative().optional() }).strict().optional().describe('Required for paid concept/build: maximum GripForge credits OR subscription USD accepted. Use the quote funding currency: insufficient AI allowance falls back to GripForge credits for the whole new job, never both. Use plan.concept_quote for images, plan.quote for Meshy. Provider credits are separate.'),
32
+ workspace_id: schema.string().max(100).optional(),
33
+ idempotency_key: schema.string().regex(/^[a-zA-Z0-9_.:-]{8,160}$/).optional(),
34
+ };
35
+ const definitions = [
36
+ { 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
+ { name: ARCHITECTURE_TOOL_NAMES[1], kind: 'building', shape: { ...shared, recipe: building,
38
+ stage: schema.enum(['plan', 'concept', 'build']).optional().describe('plan is free: exact prompts and separate image/3D quotes. concept creates reviewable workspace images. First review artistic scene, then isolated views of the SAME building, then build 3D from chosen isolated references.'),
39
+ concept_provider: schema.enum(['openai', 'xai']).optional().describe('Default openai (GPT Image 2.5 Sunburst, high quality). xai explicitly selects Imagine. Provider is pinned in the durable job; never silently falls back. Read the provider-specific concept_quote.'),
40
+ concept_presentation: schema.enum(['scene', 'isolated']).optional().describe('Default scene: artistic concept in its neighbourhood preserving the user atmosphere and lighting. isolated: technical views of the SAME building for Meshy. Keep rich architectural detail in both.'),
41
+ concept_reference: ref.optional().describe('Owned artistic reference to EDIT into new scene/isolated views. Distinct from recipe.concept, which reuses an existing front view without generation. Exclusive with recipe.concept/concepts/source and stage=build.'),
42
+ 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
+ }, 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
+ { 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.' },
45
+ ];
46
+ for (const tool of definitions)
47
+ register(tool.name, { title: tool.title, description: tool.description, inputSchema: tool.shape,
48
+ annotations: { readOnlyHint: tool.kind === 'schema', destructiveHint: false, idempotentHint: tool.kind === 'schema', openWorldHint: tool.kind !== 'schema' } }, async (args, extra) => {
49
+ const parsed = schema.object(tool.shape).strict().safeParse(args);
50
+ if (!parsed.success)
51
+ return { isError: true, content: [{ type: 'text', text: parsed.error.message }] };
52
+ const key = options.getApiKey();
53
+ if (!key && tool.kind !== 'schema')
54
+ return { isError: true, content: [{ type: 'text', text: 'GripForge API key required.' }] };
55
+ const { workspace_id, ...body } = parsed.data;
56
+ try {
57
+ const response = await fetch(options.apiUrl.replace(/\/$/, '') + '/api/v1/architecture', {
58
+ method: tool.kind === 'schema' ? 'GET' : 'POST',
59
+ 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 }) }),
61
+ signal: AbortSignal.any([AbortSignal.timeout(60_000), ...(extra?.signal ? [extra.signal] : [])]),
62
+ });
63
+ const data = await response.json();
64
+ for (const field of ['studio_url', 'status_url'])
65
+ if (typeof data[field] === 'string' && data[field].startsWith('/'))
66
+ data[field] = new URL(data[field], options.apiUrl).href;
67
+ return { ...(!response.ok ? { isError: true } : {}), structuredContent: data, content: [{ type: 'text', text: JSON.stringify(data) }] };
68
+ }
69
+ catch (error) {
70
+ return { isError: true, content: [{ type: 'text', text: error instanceof Error ? error.message : 'Architecture request failed.' }] };
71
+ }
72
+ });
73
+ }
@@ -0,0 +1,101 @@
1
+ import { z } from 'zod/v4';
2
+ /** Shared hosted/npm contract: assets are discovered independently of game modules. */
3
+ export function registerAssetProductionTools(register, options, schema = z) {
4
+ const origin = options.apiUrl.replace(/\/$/, '');
5
+ const workspace = schema.string().max(100).optional().describe('Authorized workspace; defaults to the connected workspace.');
6
+ const result = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], structuredContent: data });
7
+ async function request(path, workspaceId, method = 'GET', body) {
8
+ const key = options.getApiKey();
9
+ if (!key)
10
+ throw Error('GripForge API key required.');
11
+ const response = await fetch(origin + '/api/v1/' + path, { method, headers: { 'x-api-key': key, 'x-gripforge-client': 'mcp', ...(body ? { 'content-type': 'application/json' } : {}), ...(typeof workspaceId === 'string' ? { 'x-workspace-id': workspaceId } : {}) }, ...(body ? { body: JSON.stringify(body) } : {}), signal: AbortSignal.timeout(60000) });
12
+ const data = await response.json();
13
+ if (!response.ok)
14
+ throw Error(String(data.error ?? `HTTP ${response.status}`));
15
+ return data;
16
+ }
17
+ const wrap = (run) => async (args) => {
18
+ try {
19
+ return result(await run(args));
20
+ }
21
+ catch (e) {
22
+ return { isError: true, content: [{ type: 'text', text: e instanceof Error ? e.message : String(e) }] };
23
+ }
24
+ };
25
+ register('gripforge_generate_weapon_catalog', {
26
+ title: 'Generate a weapon catalogue',
27
+ description: 'Plan then manufacture 1–64 original static weapon models using the existing Meshy PBR pipeline. plan is free and returns an exact aggregate quote and plan_hash. build requires that hash, an explicit budget covering the quote and a stable idempotency_key. One durable job, two bounded fabrication lanes, per-weapon checkpoints and billing; cancellation/retry preserves finished work and provider task IDs. Results remain private work assets. Inspect geometry, textures and actual renderer output before publishing. Does not create attack/reload animations, grip bindings, or flexible-chain physics. Poll gripforge_generation_read; result.items contains Library IDs and Studio links.',
28
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
29
+ inputSchema: {
30
+ stage: schema.enum(['plan', 'build']), workspace_id: workspace,
31
+ entries: schema.array(schema.object({ key: schema.string().regex(/^[a-z0-9][a-z0-9_-]{0,47}$/), name: schema.string().min(1).max(160), prompt: schema.string().min(3).max(600), subtype: schema.string().min(1).max(60), style: schema.enum(['gun', 'melee', 'shield', 'staff']).optional(), polycount: schema.number().int().min(500).max(20000).optional(), tags: schema.array(schema.string().min(1).max(60)).max(16).optional() }).strict()).min(1).max(64),
32
+ plan_hash: schema.string().regex(/^[a-f0-9]{64}$/).optional(),
33
+ budget: schema.object({ credits: schema.number().nonnegative().optional(), usd: schema.number().nonnegative().optional() }).strict().optional(),
34
+ idempotency_key: schema.string().regex(/^[a-zA-Z0-9_.:-]{8,160}$/).optional(),
35
+ },
36
+ }, wrap(args => request('weapons/catalog', args.workspace_id, 'POST', args)));
37
+ register('gripforge_generation_quote', {
38
+ title: 'Estimate generation usage before spending',
39
+ description: 'Free quote for a model, concept, texture, rig or animation. Reports the account funding mode, available balance and estimated operation cost. A subscription combines AI and paid asset generation in one monthly limit without also charging generation credits. Quote every missing role, propose the plan, and obtain a generation budget before submitting paid jobs. Estimates cover one operation, not the full concept/rig/review pipeline.',
40
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
41
+ inputSchema: { kind: schema.enum(['model', 'concept', 'texture', 'rig', 'animation']), provider: schema.enum(['meshy', 'tripo', 'xai', 'openai']).optional(), count: schema.number().int().min(1).max(20).optional(), workspace_id: workspace },
42
+ }, wrap(args => {
43
+ const params = new URLSearchParams({ kind: String(args.kind), count: String(args.count ?? 1) });
44
+ if (args.provider)
45
+ params.set('provider', String(args.provider));
46
+ return request(`generation-quote?${params}`, args.workspace_id);
47
+ }));
48
+ register('gripforge_asset_search', {
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.',
51
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
52
+ inputSchema: {
53
+ q: schema.string().max(200).describe('Visual role or style, e.g. sci-fi turret, rock, soldier.'),
54
+ kind: schema.enum(['character', 'fps-arms', 'enemy', 'weapon', 'equipment', 'prop', 'texture', 'skybox', 'vfx', 'animation', 'audio', 'kit']).optional().describe('Optional asset kind.'),
55
+ source: schema.enum(['workspace', 'community', 'both']).optional().describe('Defaults to both.'),
56
+ limit: schema.number().int().min(1).max(48).optional().describe('Results per source; default 12.'),
57
+ offset: schema.number().int().min(0).max(10000).optional().describe('Pagination offset per source; default 0.'),
58
+ target: schema.enum(['mobile', 'desktop']).optional().describe('Platform the game ships on: sets the triangle and file budgets of the rank. Defaults to mobile.'),
59
+ sort: schema.enum(['rank', 'newest']).optional().describe('rank (default): best fit first; newest: most recent first.'),
60
+ workspace_id: workspace,
61
+ },
62
+ }, wrap(async (args) => {
63
+ const limit = Number(args.limit ?? 12), offset = Number(args.offset ?? 0);
64
+ const target = args.target === 'desktop' ? 'desktop' : 'mobile';
65
+ const qs = new URLSearchParams({ q: String(args.q ?? ''), limit: String(limit), offset: String(offset), sort: args.sort === 'newest' ? 'newest' : 'rank', target });
66
+ if (args.kind)
67
+ qs.set('kind', String(args.kind));
68
+ const sources = args.source === 'workspace' ? ['workspace'] : args.source === 'community' ? ['community'] : ['workspace', 'community'];
69
+ const data = await Promise.all(sources.map(async (source) => {
70
+ const response = await request(`${source === 'workspace' ? 'library' : 'community'}?${qs}`, args.workspace_id);
71
+ const items = (Array.isArray(response.items) ? response.items : []).slice(0, limit);
72
+ return { source, total: response.total ?? items.length, items: items.map(item => {
73
+ const meta = (item.meta && typeof item.meta === 'object' ? item.meta : {});
74
+ const rank = (item.rank && typeof item.rank === 'object' ? item.rank : null);
75
+ return { id: item.id, name: item.name, kind: item.kind, filename: item.filename, source,
76
+ score: rank?.score ?? null, grade: rank?.grade ?? null, reasons: (rank?.reasons ?? []).slice(0, 5).map(r => `${r.points > 0 ? '+' : ''}${r.points} ${r.note}`),
77
+ triangles: meta.triangles, tags: meta.tags, rigged: meta.rigged, placeholder: meta.placeholder === true || /proxy_|volume de travail/i.test(String(item.filename) + ' ' + String(item.name)),
78
+ next: source === 'community' ? 'gripforge_asset_clone, then gripforge_library_pull' : 'gripforge_library_get / gripforge_library_pull',
79
+ };
80
+ }) };
81
+ }));
82
+ // The top pick across sources; a workspace copy wins a tie (no credit to take it).
83
+ const best = data.flatMap(d => d.items).filter(i => typeof i.score === 'number' && !i.placeholder)
84
+ .sort((a, b) => b.score - a.score || (a.source === 'workspace' ? -1 : 1))[0] ?? null;
85
+ return { target, best, results: data, note: 'Ranked for a GripForge game (see reasons). Inspect suitability and textures/animations before importing. Downloads alone are not runtime integration.' };
86
+ }));
87
+ register('gripforge_asset_clone', {
88
+ title: 'Copy a Community asset to the workspace',
89
+ description: 'Copy a published Community asset into the connected workspace. Assets and armor sets are free by default. Only an explicit creator price is charged, using paid credits. Repeated takes are not billed again but may create another copy: reuse an existing workspace id when possible. Returns the private Library item and signed file URL. Download it with gripforge_library_pull, then import into the actual game.',
90
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
91
+ inputSchema: { id: schema.string().regex(/^lib_[A-Za-z0-9_-]+$/).describe('Published Community Library id.'), workspace_id: workspace },
92
+ }, wrap(args => request(`community/${encodeURIComponent(String(args.id))}/clone`, args.workspace_id, 'POST')));
93
+ }
94
+ /** 202 response: do not strip job ids or misrepresent a queued model as a Library item. */
95
+ export function assetGenerationOutput(schema = z) {
96
+ return schema.object({
97
+ job: schema.string().optional(), job_id: schema.string().optional(), status: schema.string().optional(), stage: schema.string().optional(),
98
+ status_url: schema.string().optional(), studio_url: schema.string().optional(), library_url: schema.string().optional(),
99
+ item: schema.record(schema.string(), schema.unknown()).optional(), provider: schema.string().optional(), notes: schema.array(schema.string()).optional(),
100
+ }).passthrough();
101
+ }
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod/v4';
2
+ export const CREATURE_RIG_TOOL_NAMES = ['gripforge_creature_rig_schema', 'gripforge_creature_analyze', 'gripforge_creature_rig'];
3
+ /** Same contract for hosted MCP and the local client; Blender runs in the durable worker. */
4
+ export function registerCreatureRigTools(register, options, schema = z) {
5
+ const shared = { workspace_id: schema.string().max(100).optional(), name: schema.string().min(1).max(100).optional(), idempotency_key: schema.string().regex(/^[a-zA-Z0-9_.:-]{8,160}$/).optional() };
6
+ const analyze = { ...shared, source_id: schema.string().regex(/^lib_[A-Za-z0-9_-]{8,64}$/), kind: schema.enum(['enemy', 'character']).optional() };
7
+ const rig = { ...shared, analysis_job: schema.string().regex(/^gen_[A-Za-z0-9_-]{8,64}$/), profile: schema.record(schema.string(), schema.unknown()).optional().describe('Optional corrected anatomy in the exact coordinate system returned by analyze. Read the schema and inspect the multiview preview first.'), reviewed_anatomy: schema.boolean().optional().describe('Required: true only after inspecting/correcting the anatomy and its skeleton overlay.'), allow_rerig: schema.boolean().optional().describe('Explicitly create a NEW rigged copy if the source already contains bones. The source is never overwritten.') };
8
+ async function call(action, args, signal) {
9
+ const key = options.getApiKey();
10
+ if (!key && action !== 'schema')
11
+ return { isError: true, content: [{ type: 'text', text: 'GripForge API key required.' }] };
12
+ const { workspace_id, ...body } = args;
13
+ try {
14
+ const response = await fetch(options.apiUrl.replace(/\/$/, '') + '/api/v1/creature-rigs', {
15
+ method: action === 'schema' ? 'GET' : 'POST', headers: { 'content-type': 'application/json', 'x-gripforge-client': 'mcp', ...(key ? { 'x-api-key': key } : {}), ...(typeof workspace_id === 'string' ? { 'x-workspace-id': workspace_id } : {}) },
16
+ ...(action === 'schema' ? {} : { body: JSON.stringify({ action, ...body }) }), signal: AbortSignal.any([AbortSignal.timeout(60_000), ...(signal ? [signal] : [])]),
17
+ });
18
+ const data = await response.json();
19
+ for (const field of ['studio_url', 'status_url'])
20
+ if (typeof data[field] === 'string' && data[field].startsWith('/'))
21
+ data[field] = new URL(data[field], options.apiUrl).href;
22
+ return { ...(!response.ok ? { isError: true } : {}), structuredContent: data, content: [{ type: 'text', text: JSON.stringify(data) }] };
23
+ }
24
+ catch (error) {
25
+ return { isError: true, content: [{ type: 'text', text: error instanceof Error ? error.message : 'Creature rig request failed.' }] };
26
+ }
27
+ }
28
+ const tools = [
29
+ { name: CREATURE_RIG_TOOL_NAMES[0], action: 'schema', title: 'Creature rig · schema', shape: {}, description: 'Read the reusable creature anatomy/rig workflow, coordinate contract, limits and export formats. Detect anatomy before authoring; source GLB, asset instances and draft rig stay separate. Blender is the worker, GripForge is the review renderer.' },
30
+ { name: CREATURE_RIG_TOOL_NAMES[1], action: 'analyze', title: 'Creature rig · detect anatomy', shape: analyze, description: 'Queue multiview mesh inspection and vision anatomy detection for an OWNED GLB. Detect actual body, legs, arms, claws, wings or tail without imposing a humanoid/eight-leg template. Returns a persistent job_id. Poll gripforge_generation_read with include_preview=true for the annotated views, editable profile and uncertainty flags. No rig mutation. 0 GripForge credits; configured server vision/Blender required.' },
31
+ { name: CREATURE_RIG_TOOL_NAMES[2], action: 'rig', title: 'Creature rig · manufacture', shape: rig, description: 'Create a NEW private draft character/enemy from a completed creature analysis and optional corrected anatomy. Blender builds the skeleton, surface-smoothed skin weights and procedural starting/diagnostic clips. Grounded leg chains get in-place walk/run with IK. Returns job_id; poll gripforge_generation_read for the Character Studio link, skinned GLB, editable .blend, anatomy and deformation report. Never automatically replaces or validates the source. Every anatomy proposal requires explicit review; use generation_cancel/retry to control durable work. Finger/facial authoring and finished combat choreography are outside v1. 0 GripForge credits.' },
32
+ ];
33
+ for (const tool of tools)
34
+ register(tool.name, { title: tool.title, description: tool.description, inputSchema: tool.shape,
35
+ annotations: { readOnlyHint: tool.action === 'schema', destructiveHint: false, idempotentHint: tool.action === 'schema', openWorldHint: false } }, async (args, extra) => {
36
+ const parsed = schema.object(tool.shape).strict().safeParse(args);
37
+ if (!parsed.success)
38
+ return { isError: true, content: [{ type: 'text', text: parsed.error.message }] };
39
+ return call(tool.action, parsed.data, extra?.signal);
40
+ });
41
+ }