voidless-mcp 1.6.15 → 1.6.17

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
@@ -113,6 +113,7 @@ Or just the hosted connector, no key: `claude mcp add --transport http voidless
113
113
  | `generate_sfx` / `generate_music` | Sound effect, or a music track with length, tempo and key |
114
114
  | `generate_video` / `extend_video` | Short clip from a prompt and reference images, or continue one |
115
115
  | `generate_3d_model` / `rig_3d_model` / `animate_3d_model` | 3D model, rig it, animate it |
116
+ | `render_3d_sprites` | A 3D model's move (or the model standing) as 2D sprites: 8, 4, platformer, isometric or front views, 16 frames, 32-512 px; free |
116
117
 
117
118
  Generation tools create new content in your library; none deletes or overwrites existing assets. Each tool's description lists its credit cost.
118
119
 
package/index.js CHANGED
@@ -19,5 +19,5 @@ if (!API_KEY) {
19
19
  process.exit(1);
20
20
  }
21
21
 
22
- const server = createVoidlessServer(API_KEY);
22
+ const server = createVoidlessServer(API_KEY, "stdio");
23
23
  await server.connect(new StdioServerTransport());
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.15",
4
+ "version": "1.6.17",
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.15";
30
+ export const VERSION = "1.6.17";
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
@@ -49,6 +49,7 @@ const GALLERY_TOOLS = [
49
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
+ "render_3d_sprites",
52
53
  ];
53
54
  const GALLERY_HTML = readFileSync(new URL("./gallery.html", import.meta.url), "utf8");
54
55
 
@@ -61,6 +62,7 @@ const LONG_PATHS = new Set([
61
62
  "/generate/image", "/generate/image-edit", "/generate/bbox-edit", "/generate/image-rotations", "/generate/image-parts",
62
63
  "/generate/image-animation", "/generate/multi-animation", "/generate/video", "/generate/video-extend",
63
64
  "/generate/sfx", "/generate/music", "/generate/3d-model", "/generate/model-rig", "/generate/model-animation",
65
+ "/generate/model-sprites",
64
66
  ]);
65
67
  const WAIT_MS = Number(process.env.VOIDLESS_WAIT_MS) || 80_000;
66
68
  const CUT_OFF = /\((502|504|520|522|524)\)|fetch failed|ECONNRESET|socket|terminated|other side closed/i;
@@ -92,11 +94,14 @@ function restoredText(result) {
92
94
  ].filter(Boolean).join("\n");
93
95
  }
94
96
 
95
- export function createVoidlessServer(API_KEY) {
97
+ // `transport` is "stdio" (index.js) or "hosted" (http.js). Every API call says it comes from the MCP
98
+ // server, so Voidless can tell these generations from the web app's and from direct API use.
99
+ export function createVoidlessServer(API_KEY, transport = "stdio") {
100
+ const CLIENT = `mcp/${VERSION} (${transport})`;
96
101
  async function api(method, path, body, headers = {}) {
97
102
  const res = await fetch(`${BASE_URL}/api${path}`, {
98
103
  method,
99
- headers: { "Content-Type": "application/json", "x-api-key": API_KEY, ...headers },
104
+ headers: { "Content-Type": "application/json", "x-api-key": API_KEY, "X-Voidless-Client": CLIENT, ...headers },
100
105
  body: body === undefined ? undefined : JSON.stringify(body),
101
106
  });
102
107
 
@@ -1237,6 +1242,50 @@ export function createVoidlessServer(API_KEY) {
1237
1242
  })
1238
1243
  );
1239
1244
 
1245
+ server.tool(
1246
+ "render_3d_sprites",
1247
+ "Render a 3D model as 2D game sprites: one of its moves (from animate_3d_model) or the model standing, " +
1248
+ "seen from 8 directions, 4, the two sides of a platformer, 4 isometric diagonals, or the front. 16 frames " +
1249
+ "by default, at 32-512 px (pixel art up to 128, from the same pass as generated sprites; smooth above). Saved as an " +
1250
+ "animated sprite asset linked to the model, one animation per direction, ready to export as a sheet or " +
1251
+ "edit. Every render of the same model goes onto that one asset. Free.",
1252
+ {
1253
+ id: z.number().int().describe("3D model asset ID"),
1254
+ animation: z
1255
+ .string()
1256
+ .optional()
1257
+ .describe("The move to render: its name ('walk'), preset or animation id. Empty = the model standing"),
1258
+ directions: z
1259
+ .enum(["eight", "four", "platformer", "isometric", "front"])
1260
+ .default("eight")
1261
+ .describe("Which views: 8 around, 4 (down/right/up/left), platformer (right, left, side-on), isometric diagonals, or the front"),
1262
+ size: z
1263
+ .union([z.literal(32), z.literal(64), z.literal(128), z.literal(256), z.literal(512)])
1264
+ .default(128)
1265
+ .describe("Sprite size in pixels: 32-128 come out as pixel art, 256 and 512 as smooth renders"),
1266
+ frames: z.number().int().min(2).max(32).default(16).describe("Frames per direction (default 16)"),
1267
+ },
1268
+ writeTool("Render 3D sprites"),
1269
+ tool(async (args) => {
1270
+ const data = await apiPost("/generate/model-sprites", args);
1271
+ const asset = await apiGet(`/assets/${data.assetId}`).catch(() => null);
1272
+ const name = String(data.animation?.name || "still").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
1273
+ const made = ((asset?.meta_data?.frames) || [])
1274
+ .filter((f) => f && f.type === "animation" && String(f.name || "").startsWith(`${name}-`))
1275
+ .slice(-(data.directions || []).length || undefined);
1276
+ const head = [
1277
+ `${data.animation ? `"${data.animation.name}"` : "The model standing"} rendered from 3D: ` +
1278
+ `${(data.directions || []).length} direction${(data.directions || []).length === 1 ? "" : "s"} ` +
1279
+ `(${(data.directions || []).join(", ")}), ${data.frameCount} frame${data.frameCount === 1 ? "" : "s"} each at ${data.size} px, ${data.fps} fps.`,
1280
+ `${data.added ? "Added to" : "Saved as"} sprite asset ${data.assetId} (${APP_URL}/asset/${data.assetId}), linked to model ${args.id}.`,
1281
+ money(data),
1282
+ ].filter(Boolean).join("\n");
1283
+ const content = [{ type: "text", text: head }];
1284
+ if (made.length) content.push(...(await frameContent(made)));
1285
+ return withGallery({ content }, made.flatMap((f) => frameItems(f, data.assetId)), "3D sprites");
1286
+ })
1287
+ );
1288
+
1240
1289
  // The inline gallery, and the tools whose results it shows.
1241
1290
  server.registerResource(
1242
1291
  "gallery",