voidless-mcp 1.0.0 → 1.6.2

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
@@ -1,16 +1,38 @@
1
1
  # Voidless MCP server
2
2
 
3
- Generate game assets from any MCP client (Claude Desktop, Claude Code, Cursor, VS Code, Windsurf and others): sprites, animations, tilesets, sound effects, music, video and 3D models, saved straight to your [Voidless](https://voidless.dev) library.
3
+ Generate game assets from any MCP client (claude.ai, ChatGPT, Claude Desktop, Claude Code, Cursor, VS Code, Windsurf and others): sprites, animations, tilesets, sound effects, music, video and 3D models, saved straight to your [Voidless](https://voidless.dev) library and shown right in the chat.
4
+
5
+ Two ways to connect: the **hosted connector** (paste a URL, sign in, nothing to install) or this **npm package** (runs locally with an API key). Setup guide: [voidless.dev/mcp](https://voidless.dev/mcp).
4
6
 
5
7
  ## Requirements
6
8
 
7
- - Node.js 18 or newer
8
- - A Voidless account on **Voidless+** and an API key (starts with `void_`), created at [voidless.dev/account](https://voidless.dev/account)
9
+ - A Voidless account on **Voidless+**
10
+ - For the npm package: Node.js 18 or newer and an API key (starts with `void_`), created at [voidless.dev/account](https://voidless.dev/account)
9
11
 
10
12
  Every generation spends credits from your Voidless account. Tool descriptions list the cost tiers where they differ.
11
13
 
12
14
  ## Setup
13
15
 
16
+ ### Hosted connector (no install)
17
+
18
+ Connector URL:
19
+
20
+ ```
21
+ https://mcp.voidless.dev/mcp
22
+ ```
23
+
24
+ - **Claude** (claude.ai and the Claude apps): **Settings → Connectors → Add custom connector**, name it Voidless and paste the URL.
25
+ - **ChatGPT** (web, paid plans): **Settings → Apps & Connectors → Advanced settings**, turn on **Developer mode**, then **Create**, paste the URL and choose **OAuth**.
26
+ - Any other MCP client that takes a remote server URL works the same way.
27
+
28
+ You'll be asked to sign in to Voidless and click **Allow**; no API key to copy. Results show inline in
29
+ hosts that support MCP Apps: images, every facing of a rotation, animations playing, and audio and video
30
+ players. To disconnect, revoke the "… connector" key under API keys on [voidless.dev/account](https://voidless.dev/account).
31
+
32
+ ### Local server (npx)
33
+
34
+ For clients that run a local server, or to work with image files on your own disk.
35
+
14
36
  Claude Desktop / Cursor / any client with a JSON config:
15
37
 
16
38
  ```json
@@ -33,28 +55,54 @@ claude mcp add voidless -e VOIDLESS_API_KEY=void_... -- npx -y voidless-mcp
33
55
 
34
56
  ## Tools
35
57
 
58
+ **Your library** (free, read-only)
59
+
60
+ | Tool | What it does |
61
+ | --- | --- |
62
+ | `list_assets` | Your assets, newest first, filterable by type or tag |
63
+ | `get_asset` | One asset with every frame: animations, 8-way rotations, edits and parts, with the ids and image keys the other tools take |
64
+ | `get_animation_presets` | The animation-set presets and what each pass costs |
65
+
66
+ **Tagging** (free)
67
+
68
+ | Tool | What it does |
69
+ | --- | --- |
70
+ | `tag_asset` | Add, remove or replace an asset's tags |
71
+ | `tag_frame` | Add, remove or replace the tags on one animation, edit or part |
72
+
73
+ **Images**
74
+
75
+ | Tool | What it does |
76
+ | --- | --- |
77
+ | `generate_image` | Sprite, character, portrait, prop, tileset, UI piece or artwork in a chosen model, style, palette, size and perspective |
78
+ | `edit_image` | Change an image with an instruction; the edit is added as a new frame |
79
+ | `rotate_sprite` | An 8-way set (front, back, sides, diagonals) in one pass |
80
+ | `extract_parts` | Split a character into body parts for rigging |
81
+ | `upload_image` | Add an image you already have to your library |
82
+ | `pixelate_image` | Convert any image to pixel art |
83
+
84
+ **Animation**
85
+
36
86
  | Tool | What it does |
37
87
  | --- | --- |
38
- | `generate_image` | Sprite, character, portrait, prop, tileset or artwork in a chosen style, palette, size and perspective |
39
- | `generate_image_edit` | Edit an image with a text instruction |
40
- | `generate_image_rotate` | Rotated views of a sprite (top-down / isometric angle sheets) |
41
- | `generate_image_parts` | Split a sprite into parts (head, body, limbs) |
42
- | `generate_image_animation` | Animate a sprite (walk, idle, ...) |
43
- | `generate_multi_animation` | Several animations for a character in one pass |
44
- | `generate_multi_animation_edit` | Edit an animation set with a text instruction |
45
- | `generate_image_animation_edit` | Edit one frame of an animation |
46
- | `generate_pixelate_image` | Convert any image to pixel art |
47
- | `upload_image` | Upload an image to your library |
48
- | `generate_sfx` | Sound effect from a prompt |
49
- | `generate_music` | Music track from a prompt |
50
- | `generate_video` / `generate_video_extend` | Short video clip, or extend one |
51
- | `generate_3d_model` / `generate_3d_model_rig` / `generate_3d_model_animation` | 3D model, rig it, animate it |
52
-
53
- Every tool creates new content in your library; none deletes or overwrites existing assets.
88
+ | `animate_sprite` | One 16-frame clip from a description of the motion |
89
+ | `generate_animation_set` | Several clips in one job: a preset (platformer, top-down walk/run/dash/attack/idle) on one sprite, or one directional clip per facing of an 8-way set; the left side is mirrored from the right (`autoFlip` to mirror any right-only clips after the run) |
90
+ | `flip_left_side` | Mirror the right-side animations into the left side (walk-right -> walk-left), free |
91
+
92
+ **Sound, video and 3D**
93
+
94
+ | Tool | What it does |
95
+ | --- | --- |
96
+ | `generate_sfx` / `generate_music` | Sound effect, or a music track with length, tempo and key |
97
+ | `generate_video` / `extend_video` | Short clip from a prompt and reference images, or continue one |
98
+ | `generate_3d_model` / `rig_3d_model` / `animate_3d_model` | 3D model, rig it, animate it |
99
+
100
+ Generation tools create new content in your library; none deletes or overwrites existing assets. Each tool's description lists its credit cost.
54
101
 
55
102
  ## Example prompts
56
103
 
57
- - "Make a 64px pixel-art knight, side view, then animate a walk cycle."
104
+ - "Make a 64px pixel-art knight, side view, turn it into an 8-way set and give it a top-down walk in every direction."
105
+ - "Mirror my knight's right-facing animations into the left side."
58
106
  - "Generate a top-down grass and dirt tileset in a gameboy palette."
59
107
  - "Give me a coin pickup sound effect and a short chiptune loop for the title screen."
60
108
  - "Create a low-poly treasure chest 3D model, rig it and animate it opening."
package/gallery.html ADDED
@@ -0,0 +1,176 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Voidless results</title>
7
+ <style>
8
+ /* The inline view an MCP host draws under a Voidless tool call: what the tool made, shown. */
9
+ :root {
10
+ --bg: transparent; --fg: #1d1b20; --muted: #6b6672; --tile: #ecebef; --line: #dcdae0; --accent: #5b5bd6;
11
+ --check: #e3e1e7;
12
+ }
13
+ :root[data-theme="dark"] { --fg: #ecebef; --muted: #a29fa8; --tile: #26252b; --line: #34323a; --accent: #9b9bf0; --check: #2e2d34; }
14
+ @media (prefers-color-scheme: dark) {
15
+ :root:not([data-theme="light"]) { --fg: #ecebef; --muted: #a29fa8; --tile: #26252b; --line: #34323a; --accent: #9b9bf0; --check: #2e2d34; }
16
+ }
17
+ * { box-sizing: border-box; }
18
+ html, body { margin: 0; background: var(--bg); color: var(--fg); font: 14px/1.4 system-ui, -apple-system, "Segoe UI", sans-serif; }
19
+ .wrap { padding: 4px 2px 8px; display: grid; gap: 8px; }
20
+ .head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; font-size: 13px; color: var(--muted); }
21
+ .head a { color: var(--accent); text-decoration: none; }
22
+ .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; align-items: start; }
23
+ figure.audio { grid-column: 1 / -1; display: flex; align-items: center; gap: 10px; padding: 6px 10px; }
24
+ figure.audio .media { aspect-ratio: auto; background: none; flex: 1; }
25
+ figure.audio .media audio { width: 100%; }
26
+ figure.audio figcaption { order: -1; padding: 0; min-width: 120px; max-width: 40%; color: var(--fg); }
27
+ figure.video { grid-column: span 2; }
28
+ .grid.one { grid-template-columns: minmax(0, 360px); }
29
+ figure { margin: 0; border-radius: 10px; overflow: hidden; border: 1px solid var(--line); background: var(--tile); }
30
+ .media { aspect-ratio: 1; display: grid; place-items: center;
31
+ background: repeating-conic-gradient(var(--check) 0 25%, transparent 0 50%) 50% / 16px 16px; }
32
+ .media img, .media video { width: 100%; height: 100%; object-fit: contain; display: block; image-rendering: auto; }
33
+ .media.pixel img { image-rendering: pixelated; }
34
+ .media.wide { aspect-ratio: 16 / 9; }
35
+ .media audio { width: 92%; }
36
+ figcaption { padding: 5px 8px; font-size: 12px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
37
+ .empty { color: var(--muted); font-size: 13px; padding: 6px 2px; }
38
+ </style>
39
+ </head>
40
+ <body>
41
+ <div class="wrap" id="app"><div class="empty">Waiting for the result…</div></div>
42
+ <script>
43
+ (() => {
44
+ const app = document.getElementById("app");
45
+ let assetLink = "";
46
+
47
+ // ---- host messaging: MCP Apps (JSON-RPC over postMessage) and ChatGPT's window.openai ----
48
+ let nextId = 1;
49
+ const pending = new Map();
50
+ const post = (msg) => window.parent && window.parent.postMessage(msg, "*");
51
+ const request = (method, params) => new Promise((resolve) => {
52
+ const id = nextId++; pending.set(id, resolve); post({ jsonrpc: "2.0", id, method, params });
53
+ });
54
+ const notify = (method, params) => post({ jsonrpc: "2.0", method, params });
55
+
56
+ function openLink(url) {
57
+ if (window.openai && typeof window.openai.openExternal === "function") return window.openai.openExternal({ href: url });
58
+ request("ui/open-link", { url }).catch(() => window.open(url, "_blank", "noopener"));
59
+ }
60
+
61
+ function applyTheme(theme) {
62
+ if (theme === "dark" || theme === "light") document.documentElement.dataset.theme = theme;
63
+ }
64
+
65
+ window.addEventListener("message", (e) => {
66
+ const m = e.data;
67
+ if (!m || m.jsonrpc !== "2.0") return;
68
+ if (m.id && pending.has(m.id)) { pending.get(m.id)(m.result); pending.delete(m.id); return; }
69
+ if (m.method === "ui/notifications/tool-result") render(m.params);
70
+ if (m.method === "ui/notifications/host-context-changed") applyTheme(m.params && m.params.theme);
71
+ });
72
+
73
+ // ---- rendering ----
74
+ const el = (tag, attrs = {}, kids = []) => {
75
+ const n = document.createElement(tag);
76
+ for (const [k, v] of Object.entries(attrs)) if (v !== undefined && v !== null) n.setAttribute(k, v);
77
+ for (const k of kids) n.append(k);
78
+ return n;
79
+ };
80
+
81
+ function tile(item, single) {
82
+ const media = el("div", { class: "media" + (item.kind === "video" ? " wide" : "") });
83
+ if (item.kind === "animation") {
84
+ const img = el("img", { src: item.url, alt: item.label || "" });
85
+ img.addEventListener("load", () => {
86
+ if (img.naturalWidth && img.naturalWidth <= 128) media.classList.add("pixel");
87
+ sizeChanged();
88
+ }, { once: true });
89
+ media.append(img);
90
+ play(img, item.sequence);
91
+ } else if (item.kind === "image" || item.kind === "gif") {
92
+ const img = el("img", { src: item.url, alt: item.label || "", loading: "lazy" });
93
+ img.addEventListener("load", () => {
94
+ if (img.naturalWidth && img.naturalWidth <= 128) media.classList.add("pixel"); // pixel art stays crisp
95
+ sizeChanged();
96
+ });
97
+ media.append(img);
98
+ } else if (item.kind === "video") {
99
+ media.append(el("video", { src: item.url, controls: "", loop: "", muted: "", playsinline: "", autoplay: "" }));
100
+ } else if (item.kind === "audio") {
101
+ media.classList.add("wide");
102
+ media.append(el("audio", { src: item.url, controls: "" }));
103
+ }
104
+ const fig = el("figure", { class: item.kind }, [media]);
105
+ if (item.label) fig.append(el("figcaption", { title: item.label }, [item.label + (item.frames ? ` · ${item.frames} frames` : "")]));
106
+ if (item.link && item.kind !== "audio" && item.kind !== "video") {
107
+ fig.style.cursor = "pointer";
108
+ fig.addEventListener("click", () => openLink(item.link));
109
+ }
110
+ return fig;
111
+ }
112
+
113
+ // Flipbook: preload every frame, then loop at 12 fps (what the app's previews use).
114
+ const FPS = 12;
115
+ const players = [];
116
+ function play(img, urls) {
117
+ const frames = urls.map((u) => { const i = new Image(); i.src = u; return i; });
118
+ players.push({ img, urls, frames, at: 0 });
119
+ }
120
+ setInterval(() => {
121
+ for (const p of players) {
122
+ if (!p.frames.every((f) => f.complete)) continue;
123
+ p.at = (p.at + 1) % p.urls.length;
124
+ p.img.src = p.urls[p.at];
125
+ }
126
+ }, 1000 / FPS);
127
+
128
+ function render(result) {
129
+ const sc = (result && (result.structuredContent || result)) || {};
130
+ const items = Array.isArray(sc.gallery) ? sc.gallery : [];
131
+ app.replaceChildren();
132
+ players.length = 0;
133
+ if (!items.length) { sizeChanged(); return; }
134
+ assetLink = (items.find((i) => i.link) || {}).link || "";
135
+ const head = el("div", { class: "head" }, [el("span", {}, [sc.title || "Result"])]);
136
+ if (assetLink) {
137
+ const a = el("a", { href: assetLink }, ["Open in Voidless ↗"]);
138
+ a.addEventListener("click", (e) => { e.preventDefault(); openLink(assetLink); });
139
+ head.append(a);
140
+ }
141
+ const grid = el("div", { class: "grid" + (items.length === 1 ? " one" : "") }, items.map((i) => tile(i, items.length === 1)));
142
+ app.append(head, grid);
143
+ sizeChanged();
144
+ }
145
+
146
+ let lastH = 0;
147
+ function sizeChanged() {
148
+ const h = Math.ceil(document.documentElement.getBoundingClientRect().height);
149
+ if (h === lastH) return;
150
+ lastH = h;
151
+ notify("ui/notifications/size-changed", { height: h });
152
+ }
153
+ new ResizeObserver(sizeChanged).observe(document.body);
154
+
155
+ // ---- start ----
156
+ if (window.openai) {
157
+ applyTheme(window.openai.theme);
158
+ if (window.openai.toolOutput) render({ structuredContent: window.openai.toolOutput });
159
+ window.addEventListener("openai:set_globals", (e) => {
160
+ const g = (e.detail && e.detail.globals) || {};
161
+ if (g.theme) applyTheme(g.theme);
162
+ if (g.toolOutput) render({ structuredContent: g.toolOutput });
163
+ });
164
+ }
165
+ request("ui/initialize", {
166
+ protocolVersion: "2026-01-26",
167
+ appInfo: { name: "voidless-gallery", version: "1" },
168
+ appCapabilities: {},
169
+ }).then((res) => {
170
+ applyTheme(res && res.hostContext && res.hostContext.theme);
171
+ notify("ui/notifications/initialized", {});
172
+ });
173
+ })();
174
+ </script>
175
+ </body>
176
+ </html>