voidless-mcp 1.6.14 → 1.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/package.json +1 -1
  3. package/tools.js +74 -9
package/README.md CHANGED
@@ -89,6 +89,8 @@ Or just the hosted connector, no key: `claude mcp add --transport http voidless
89
89
  | --- | --- |
90
90
  | `generate_image` | Sprite, character, portrait, prop, tileset, UI piece or artwork in a chosen model, style, palette, size and perspective; artwork can be 16:9 for backdrops |
91
91
  | `edit_image` | Change an image with an instruction; the edit is added as a new frame |
92
+ | `make_normal_map` | A normal map for an image, an edit or every frame of an animation, for 2D lights in a game engine; free |
93
+ | `make_glow_map` | The light an edit added (lit windows, a night version's lamps) on its own, for an additive glow layer; free |
92
94
  | `rotate_sprite` | An 8-way set (front, back, sides, diagonals) in one pass |
93
95
  | `extract_parts` | Split a character into body parts for rigging |
94
96
  | `upload_image` | Add an image you already have to your library |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "voidless-mcp",
3
3
  "mcpName": "dev.voidless/voidless",
4
- "version": "1.6.14",
4
+ "version": "1.6.15",
5
5
  "description": "MCP server for Voidless: generate game sprites, animations, sound, music, video and 3D models",
6
6
  "license": "MIT",
7
7
  "author": "Voidless Dev Inc. <contact@voidless.dev>",
package/tools.js CHANGED
@@ -27,7 +27,7 @@ import { Agent, setGlobalDispatcher } from "undici";
27
27
  // server does.
28
28
  setGlobalDispatcher(new Agent({ headersTimeout: 0, bodyTimeout: 0, connectTimeout: 30_000 }));
29
29
 
30
- export const VERSION = "1.6.14";
30
+ export const VERSION = "1.6.15";
31
31
  const BASE_URL = process.env.VOIDLESS_API_URL || "https://api.voidless.dev";
32
32
  const CDN_URL = "https://cdn.voidless.dev";
33
33
  // Where asset links point. Results link to the asset's page in the app rather than to raw GIFs: the
@@ -46,7 +46,7 @@ const MAX_INLINE_IMAGES = 8;
46
46
  const GALLERY_URI = "ui://voidless/gallery.html";
47
47
  const GALLERY_MIME = "text/html;profile=mcp-app";
48
48
  const GALLERY_TOOLS = [
49
- "generate_image", "upload_image", "edit_image", "rotate_sprite", "animate_sprite", "generate_animation_set",
49
+ "generate_image", "upload_image", "edit_image", "make_normal_map", "make_glow_map", "rotate_sprite", "animate_sprite", "generate_animation_set",
50
50
  "flip_left_side", "generate_video", "generate_sfx", "generate_music", "generate_3d_model", "get_asset",
51
51
  "list_assets", "extract_parts", "pixelate_image", "extend_video", "clean_up_sprite", "check_generation",
52
52
  ];
@@ -58,7 +58,7 @@ const GALLERY_HTML = readFileSync(new URL("./gallery.html", import.meta.url), "u
58
58
  // job is still going the tool says so and check_generation fetches the result later (Strapi keeps
59
59
  // it under the key: GET /generate/job/:key).
60
60
  const LONG_PATHS = new Set([
61
- "/generate/image", "/generate/image-edit", "/generate/image-rotations", "/generate/image-parts",
61
+ "/generate/image", "/generate/image-edit", "/generate/bbox-edit", "/generate/image-rotations", "/generate/image-parts",
62
62
  "/generate/image-animation", "/generate/multi-animation", "/generate/video", "/generate/video-extend",
63
63
  "/generate/sfx", "/generate/music", "/generate/3d-model", "/generate/model-rig", "/generate/model-animation",
64
64
  ]);
@@ -841,6 +841,65 @@ export function createVoidlessServer(API_KEY) {
841
841
  tool(async (args) => framesResult(await apiPost("/generate/image-edit", { ...args, model: "p-image" }), args.id, "Edit"))
842
842
  );
843
843
 
844
+ server.tool(
845
+ "make_normal_map",
846
+ "Make a normal map for an image asset, for 2D lights in a game engine: which way each pixel faces, so a " +
847
+ "moving light shades it. The shape comes from a depth model, the drawn detail from the picture. PNG at the " +
848
+ "picture's own size, OpenGL style (green = up: Godot and Unity read it as is; flip green for Unreal). " +
849
+ "Pass frameId (from get_asset) for an edit, or an animation (one map per frame); leave it out for the " +
850
+ "image itself. Free.",
851
+ {
852
+ id: assetId,
853
+ frameId: z
854
+ .number()
855
+ .int()
856
+ .optional()
857
+ .describe("Optional: the frame (an edit or an animation, from get_asset) to make it for. Empty = the image itself."),
858
+ },
859
+ writeTool("Make normal map"),
860
+ tool(async ({ id, frameId: fid }) => {
861
+ const data = await apiPost("/generate/normal-map", { id, ...(fid === undefined ? {} : { frameId: fid }) });
862
+ const keys = data.maps?.normals || (data.maps?.normal ? [data.maps.normal] : []);
863
+ const content = [
864
+ {
865
+ type: "text",
866
+ text:
867
+ `Normal map${keys.length > 1 ? `s (${keys.length}, one per frame)` : ""} saved on asset ${id} ` +
868
+ `(${APP_URL}/asset/${id}):\n` + keys.map((k) => `${CDN_URL}/${k}`).join("\n"),
869
+ },
870
+ ];
871
+ const image = keys.length ? await inlineImage(keys[0]) : null;
872
+ if (image) content.push(image);
873
+ return { content, structuredContent: { summary: content[0].text } };
874
+ })
875
+ );
876
+
877
+ server.tool(
878
+ "make_glow_map",
879
+ "Make a glow map from an edit that switched lights on (or made a night version with lights glowing): the " +
880
+ "light the edit added over the picture it came from, on transparency, for an additive or emissive layer in " +
881
+ "a game. Same size as the picture. Pass the edit's frameId (from get_asset). Free.",
882
+ {
883
+ id: assetId,
884
+ frameId: z.number().int().describe("The edit (a frame from get_asset) whose light to keep"),
885
+ },
886
+ writeTool("Make glow map"),
887
+ tool(async ({ id, frameId: fid }) => {
888
+ const data = await apiPost("/generate/glow-map", { id, frameId: fid });
889
+ const keys = data.maps?.glows || (data.maps?.glow ? [data.maps.glow] : []);
890
+ const content = [
891
+ {
892
+ type: "text",
893
+ text: `Glow map${keys.length > 1 ? `s (${keys.length})` : ""} saved on asset ${id} (${APP_URL}/asset/${id}):\n` +
894
+ keys.map((k) => `${CDN_URL}/${k}`).join("\n"),
895
+ },
896
+ ];
897
+ const image = keys.length ? await inlineImage(keys[0]) : null;
898
+ if (image) content.push(image);
899
+ return { content, structuredContent: { summary: content[0].text } };
900
+ })
901
+ );
902
+
844
903
  server.tool(
845
904
  "rotate_sprite",
846
905
  "Turn a sprite into an 8-way set (front, back, sides and diagonals) in one pass. The result is a " +
@@ -1141,11 +1200,10 @@ export function createVoidlessServer(API_KEY) {
1141
1200
 
1142
1201
  server.tool(
1143
1202
  "rig_3d_model",
1144
- "Add a skeleton to a humanoid 3D model so it can be animated (non-humanoids are refused before any " +
1145
- "charge). Run before animate_3d_model. 140 credits.",
1203
+ "Add a skeleton to a 3D model so it can be animated: a humanoid, a four-legged animal, or a prop's " +
1204
+ "moving parts (a chest lid, a door, a tree's trunk). Run before animate_3d_model. 30 credits.",
1146
1205
  {
1147
1206
  id: z.number().int().describe("3D model asset ID"),
1148
- height_meters: z.number().min(0.2).max(10).default(1.7).describe("Character height in metres"),
1149
1207
  },
1150
1208
  writeTool("Rig 3D model"),
1151
1209
  tool(async (args) => {
@@ -1156,11 +1214,18 @@ export function createVoidlessServer(API_KEY) {
1156
1214
 
1157
1215
  server.tool(
1158
1216
  "animate_3d_model",
1159
- "Animate a rigged 3D model from a description of the motion. 30 credits.",
1217
+ "Animate a rigged 3D model. Either a ready-made move (free; characters: idle, walk, run, jump, attack, " +
1218
+ "punch, kick, wave, hurt, death, cast, dance; four-legged animals: idle, walk, run, jump, attack, sit, hurt, " +
1219
+ "death), a move copied from another of the user's rigged models (free: from_id + from_animation), or a " +
1220
+ "description of any motion ('wag its tail', 'open the lid', 20 credits). Returns the clip, a GLB of the " +
1221
+ "skeleton's motion that plays on the rigged model.",
1160
1222
  {
1161
1223
  id: z.number().int().describe("Rigged 3D model asset ID"),
1162
- prompt: z.string().describe("The motion, e.g. 'walk cycle', 'idle breathing'"),
1163
- duration: z.number().min(1).max(10).default(3).describe("Seconds"),
1224
+ preset: z.string().optional().describe("Ready-made move id, e.g. 'walk' (free)"),
1225
+ from_id: z.number().int().optional().describe("Copy a move from this other rigged model (free)"),
1226
+ from_animation: z.number().int().optional().describe("The animation id on from_id to copy"),
1227
+ prompt: z.string().optional().describe("Or describe the motion, e.g. 'a spinning kick' (20 credits)"),
1228
+ duration: z.number().min(1).max(10).optional().describe("Seconds, for a described motion (default: its natural length)"),
1164
1229
  },
1165
1230
  writeTool("Animate 3D model"),
1166
1231
  tool(async (args) => {