pixelorama-mcp 0.1.0
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 +82 -0
- package/cli.js +120 -0
- package/index.js +125 -0
- package/package.json +17 -0
- package/test.js +9 -0
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Pixelorama MCP (Node.js client)
|
|
2
|
+
|
|
3
|
+
This package provides a **Node.js client** for the **Pixelorama Model Communication Protocol (MCP)** that is implemented inside Pixelorama as a GDScript WebSocket server (see `addons/mcp`).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
```bash
|
|
7
|
+
npm install --save path/to/Pixelorama/mcp
|
|
8
|
+
# or, from the repository root:
|
|
9
|
+
npm install ./mcp
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
```js
|
|
14
|
+
const { PixeloramaMCP } = require('pixelorama-mcp');
|
|
15
|
+
|
|
16
|
+
(async () => {
|
|
17
|
+
const mcp = new PixeloramaMCP(); // defaults to ws://127.0.0.1:8080
|
|
18
|
+
await mcp.connect();
|
|
19
|
+
|
|
20
|
+
// Create a fresh project
|
|
21
|
+
await mcp.newProject();
|
|
22
|
+
|
|
23
|
+
// Add five frames
|
|
24
|
+
for (let i = 0; i < 5; i++) await mcp.addFrame();
|
|
25
|
+
|
|
26
|
+
// Set playback speed to 24 FPS
|
|
27
|
+
await mcp.setFPS(24);
|
|
28
|
+
|
|
29
|
+
// Enable onion‑skinning and configure it
|
|
30
|
+
await mcp.toggleOnion(true);
|
|
31
|
+
await mcp.setOnionPastRate(2);
|
|
32
|
+
await mcp.setOnionFutureRate(2);
|
|
33
|
+
await mcp.setOnionOpacity(0.7);
|
|
34
|
+
|
|
35
|
+
// Export as an animated GIF
|
|
36
|
+
await mcp.export('GIF', 'user://exports/animation.gif');
|
|
37
|
+
|
|
38
|
+
// Query the current project state
|
|
39
|
+
const status = await mcp.queryStatus();
|
|
40
|
+
console.log('Project status:', status.project_status);
|
|
41
|
+
})();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
All methods return **Promises** that resolve with the JSON reply from the MCP server. Errors are thrown as exceptions.
|
|
45
|
+
|
|
46
|
+
## API reference
|
|
47
|
+
| Method | Parameters | Description |
|
|
48
|
+
|--------|------------|-------------|
|
|
49
|
+
| `loadProject(path)` | `path: string` | Load a `.pxo` project file. |
|
|
50
|
+
| `newProject()` | – | Create an empty project. |
|
|
51
|
+
| `addFrame()` | – | Append a new frame to the timeline. |
|
|
52
|
+
| `removeFrame(index)` | `index: number` | Delete the frame at *index*. |
|
|
53
|
+
| `setFrameDuration(index, seconds)` | `index: number`, `seconds: number` | Set duration of a single frame. |
|
|
54
|
+
| `setFPS(value)` | `value: number` | Change the project's frames‑per‑second. |
|
|
55
|
+
| `toggleOnion(enabled)` | `enabled: boolean` | Turn onion‑skinning on/off. |
|
|
56
|
+
| `setOnionPastRate(rate)` | `rate: number` | Number of past frames shown. |
|
|
57
|
+
| `setOnionFutureRate(rate)` | `rate: number` | Number of future frames shown. |
|
|
58
|
+
| `setOnionOpacity(value)` | `value: number (0‑1)` | Opacity of onion layers. |
|
|
59
|
+
| `setOnionBlueRed(enabled)` | `enabled: boolean` | Enable blue‑red colour mode. |
|
|
60
|
+
| `export(format, output)` | `format: string`, `output: string` | Export the current project. Supported formats: PNG, WEBP, JPEG, EXR, GIF, APNG, MP4, AVI, OGV, MKV, WEBM. |
|
|
61
|
+
| `saveProject(path)` | `path: string` | Save the current project as a `.pxo`. |
|
|
62
|
+
| `queryStatus()` | – | Return a snapshot of the current project (frames, FPS, onion settings, …). |
|
|
63
|
+
|
|
64
|
+
The client also emits any **event** messages the server pushes (e.g., `project_saved`) via the standard `EventEmitter` interface:
|
|
65
|
+
```js
|
|
66
|
+
mcp.on('project_saved', (msg) => console.log('saved to', msg.path));
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
> **Note**: The MCP server must be active inside Pixelorama (enable the "Pixelorama MCP" plugin under *Project → Project Settings → Plugins*). The server runs on port `8080` by default; you can change it by editing `addons/mcp/MCPServer.gd` (variable `port`).
|
|
72
|
+
|
|
73
|
+
### Uso desde la línea de comandos
|
|
74
|
+
Una vez que el servidor está corriendo, puedes usar el comando **pixelorama-mcp** que se instala con este paquete:
|
|
75
|
+
```
|
|
76
|
+
pixelorama-mcp new-project # crea un proyecto nuevo
|
|
77
|
+
pixelorama-mcp add-frame # añade un frame
|
|
78
|
+
pixelorama-mcp set-fps 24 # establece 24 FPS
|
|
79
|
+
pixelorama-mcp export GIF path/to/out.gif # exporta a GIF
|
|
80
|
+
```
|
|
81
|
+
Estos comandos pueden ser invocados desde scripts, CI pipelines o herramientas de IA como Codex/Claude.
|
|
82
|
+
|
package/cli.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { PixeloramaMCP } = require('./index');
|
|
4
|
+
|
|
5
|
+
// Simple argument parser (no external deps)
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
|
|
8
|
+
function usage() {
|
|
9
|
+
console.log(`Pixelorama MCP CLI
|
|
10
|
+
|
|
11
|
+
Usage: pixelorama-mcp [options] <command> [params]
|
|
12
|
+
|
|
13
|
+
Options:
|
|
14
|
+
--host <host> MCP server host (default: 127.0.0.1)
|
|
15
|
+
--port <port> MCP server port (default: 8080)
|
|
16
|
+
|
|
17
|
+
Commands:
|
|
18
|
+
new-project Create a new project
|
|
19
|
+
load-project <path> Load a project from path
|
|
20
|
+
add-frame Add a new frame
|
|
21
|
+
remove-frame <index> Remove frame at index (0‑based)
|
|
22
|
+
set-fps <value> Set frames‑per‑second
|
|
23
|
+
toggle-onion <true|false> Enable/disable onion‑skin
|
|
24
|
+
export <format> <output> Export (e.g., GIF, PNG) to output path
|
|
25
|
+
save-project <path> Save current project
|
|
26
|
+
query-status Get server status (debug)
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
pixelorama-mcp new-project
|
|
30
|
+
pixelorama-mcp set-fps 24
|
|
31
|
+
pixelorama-mcp export GIF user://exports/anim.gif
|
|
32
|
+
`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Parse options
|
|
37
|
+
let host = '127.0.0.1';
|
|
38
|
+
let port = 8080;
|
|
39
|
+
let i = 0;
|
|
40
|
+
while (i < args.length && args[i].startsWith('--')) {
|
|
41
|
+
const opt = args[i];
|
|
42
|
+
if (opt === '--host') {
|
|
43
|
+
host = args[i + 1];
|
|
44
|
+
i += 2;
|
|
45
|
+
} else if (opt === '--port') {
|
|
46
|
+
port = parseInt(args[i + 1], 10);
|
|
47
|
+
i += 2;
|
|
48
|
+
} else {
|
|
49
|
+
console.error('Unknown option:', opt);
|
|
50
|
+
usage();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (i >= args.length) usage();
|
|
55
|
+
|
|
56
|
+
const command = args[i];
|
|
57
|
+
const params = args.slice(i + 1);
|
|
58
|
+
|
|
59
|
+
(async () => {
|
|
60
|
+
const mcp = new PixeloramaMCP(`tcp://${host}:${port}`);
|
|
61
|
+
try {
|
|
62
|
+
await mcp.connect();
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error('Connection error:', e.message);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
switch (command) {
|
|
70
|
+
case 'new-project':
|
|
71
|
+
await mcp.newProject();
|
|
72
|
+
console.log('Created new project');
|
|
73
|
+
break;
|
|
74
|
+
case 'load-project':
|
|
75
|
+
if (!params[0]) throw new Error('Missing path');
|
|
76
|
+
await mcp.loadProject(params[0]);
|
|
77
|
+
console.log('Project loaded');
|
|
78
|
+
break;
|
|
79
|
+
case 'add-frame':
|
|
80
|
+
await mcp.addFrame();
|
|
81
|
+
console.log('Frame added');
|
|
82
|
+
break;
|
|
83
|
+
case 'remove-frame':
|
|
84
|
+
if (params[0] === undefined) throw new Error('Missing index');
|
|
85
|
+
await mcp.removeFrame(parseInt(params[0], 10));
|
|
86
|
+
console.log('Frame removed');
|
|
87
|
+
break;
|
|
88
|
+
case 'set-fps':
|
|
89
|
+
if (!params[0]) throw new Error('Missing FPS value');
|
|
90
|
+
await mcp.setFPS(parseInt(params[0], 10));
|
|
91
|
+
console.log('FPS set');
|
|
92
|
+
break;
|
|
93
|
+
case 'toggle-onion':
|
|
94
|
+
if (!params[0]) throw new Error('Missing true/false');
|
|
95
|
+
await mcp.toggleOnion(params[0] === 'true');
|
|
96
|
+
console.log('Onion‑skin toggled');
|
|
97
|
+
break;
|
|
98
|
+
case 'export':
|
|
99
|
+
if (!params[0] || !params[1]) throw new Error('Missing format or output');
|
|
100
|
+
await mcp.export(params[0], params[1]);
|
|
101
|
+
console.log('Exported');
|
|
102
|
+
break;
|
|
103
|
+
case 'save-project':
|
|
104
|
+
if (!params[0]) throw new Error('Missing path');
|
|
105
|
+
await mcp.saveProject(params[0]);
|
|
106
|
+
console.log('Project saved');
|
|
107
|
+
break;
|
|
108
|
+
case 'query-status':
|
|
109
|
+
const status = await mcp.queryStatus();
|
|
110
|
+
console.log('Status:', JSON.stringify(status, null, 2));
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
console.error('Unknown command:', command);
|
|
114
|
+
usage();
|
|
115
|
+
}
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.error('Error:', err.message);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
})();
|
package/index.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const net = require('net');
|
|
2
|
+
const EventEmitter = require('events');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* PixeloramaMCP – simple client for the Pixelorama Model Communication Protocol.
|
|
6
|
+
*
|
|
7
|
+
* Usage example (async/await):
|
|
8
|
+
* const { PixeloramaMCP } = require('pixelorama-mcp');
|
|
9
|
+
* const mcp = new PixeloramaMCP();
|
|
10
|
+
* await mcp.connect();
|
|
11
|
+
* await mcp.newProject();
|
|
12
|
+
* await mcp.addFrame();
|
|
13
|
+
* await mcp.setFPS(24);
|
|
14
|
+
* await mcp.toggleOnion(true);
|
|
15
|
+
* await mcp.export('GIF', 'user://exports/anim.gif');
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
class PixeloramaMCP extends EventEmitter {
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} url WebSocket URL of the MCP server inside Pixelorama.
|
|
21
|
+
*/
|
|
22
|
+
constructor(url = 'tcp://127.0.0.1:8080') {
|
|
23
|
+
super();
|
|
24
|
+
this.url = url;
|
|
25
|
+
this.ws = null;
|
|
26
|
+
this._pending = new Map(); // id -> {resolve, reject}
|
|
27
|
+
this._nextId = 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Connect to the MCP server. */
|
|
31
|
+
async connect() {
|
|
32
|
+
// Support only TCP connections for the built-in server.
|
|
33
|
+
if (this.ws && !this.ws.destroyed) return;
|
|
34
|
+
// Parse tcp://host:port
|
|
35
|
+
const addr = this.url.replace(/^tcp:\/\//, '');
|
|
36
|
+
const [host, portStr] = addr.split(':');
|
|
37
|
+
const port = parseInt(portStr, 10);
|
|
38
|
+
this.ws = new net.Socket();
|
|
39
|
+
// Buffer for incomplete lines.
|
|
40
|
+
this._buffer = '';
|
|
41
|
+
await new Promise((resolve, reject) => {
|
|
42
|
+
this.ws.connect(port, host, resolve);
|
|
43
|
+
this.ws.on('error', reject);
|
|
44
|
+
});
|
|
45
|
+
this.ws.on('data', (data) => {
|
|
46
|
+
this._buffer += data.toString();
|
|
47
|
+
let index;
|
|
48
|
+
while ((index = this._buffer.indexOf('\n')) >= 0) {
|
|
49
|
+
const line = this._buffer.slice(0, index);
|
|
50
|
+
this._buffer = this._buffer.slice(index + 1);
|
|
51
|
+
if (line) this._handleMessage(line);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
this.ws.on('close', () => {
|
|
55
|
+
// Reject any pending promises when the connection drops.
|
|
56
|
+
for (const { reject } of this._pending.values()) {
|
|
57
|
+
reject(new Error('TCP connection closed'));
|
|
58
|
+
}
|
|
59
|
+
this._pending.clear();
|
|
60
|
+
this.emit('close');
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_handleMessage(data) {
|
|
65
|
+
let msg;
|
|
66
|
+
try {
|
|
67
|
+
msg = JSON.parse(data);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
// Ignore malformed messages.
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Resolve pending request if it contains an id.
|
|
73
|
+
if (msg.id && this._pending.has(msg.id)) {
|
|
74
|
+
const { resolve, reject } = this._pending.get(msg.id);
|
|
75
|
+
this._pending.delete(msg.id);
|
|
76
|
+
if (msg.status === 'ok') {
|
|
77
|
+
resolve(msg);
|
|
78
|
+
} else {
|
|
79
|
+
reject(new Error(msg.message || 'MCP error'));
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Emit unsolicited events so the user can listen.
|
|
84
|
+
if (msg.event) {
|
|
85
|
+
this.emit(msg.event, msg);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Send a command and return a promise that resolves with the reply. */
|
|
90
|
+
_send(command, params = {}) {
|
|
91
|
+
const id = this._nextId++;
|
|
92
|
+
const payload = Object.assign({ command, id }, params);
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
this._pending.set(id, { resolve, reject });
|
|
95
|
+
try {
|
|
96
|
+
this.ws.write(JSON.stringify(payload) + "\n");
|
|
97
|
+
} catch (e) {
|
|
98
|
+
this._pending.delete(id);
|
|
99
|
+
reject(e);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// -----------------------------------------------------------------
|
|
105
|
+
// API methods – each returns a Promise that resolves to the server reply.
|
|
106
|
+
// -----------------------------------------------------------------
|
|
107
|
+
loadProject(path) { return this._send('load_project', { path }); }
|
|
108
|
+
newProject() { return this._send('new_project'); }
|
|
109
|
+
addFrame() { return this._send('add_frame'); }
|
|
110
|
+
removeFrame(index) { return this._send('remove_frame', { index }); }
|
|
111
|
+
setFrameDuration(index, seconds) {
|
|
112
|
+
return this._send('set_frame_duration', { index, seconds });
|
|
113
|
+
}
|
|
114
|
+
setFPS(value) { return this._send('set_fps', { value }); }
|
|
115
|
+
toggleOnion(enabled) { return this._send('toggle_onion', { enabled }); }
|
|
116
|
+
setOnionPastRate(rate) { return this._send('set_onion_past_rate', { rate }); }
|
|
117
|
+
setOnionFutureRate(rate) { return this._send('set_onion_future_rate', { rate }); }
|
|
118
|
+
setOnionOpacity(value) { return this._send('set_onion_opacity', { value }); }
|
|
119
|
+
setOnionBlueRed(enabled) { return this._send('set_onion_blue_red', { enabled }); }
|
|
120
|
+
export(format, output) { return this._send('export', { format, output }); }
|
|
121
|
+
saveProject(path) { return this._send('save_project', { path }); }
|
|
122
|
+
queryStatus() { return this._send('query_status'); }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = { PixeloramaMCP };
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pixelorama-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Node.js client for Pixelorama Model Communication Protocol (MCP). Allows external scripts to control Pixelorama via WebSocket.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"pixelorama-mcp": "cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node index.js"
|
|
11
|
+
},
|
|
12
|
+
"author": "OpenAI",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"ws": "^8.13.0"
|
|
16
|
+
}
|
|
17
|
+
}
|
package/test.js
ADDED