@iborymagic/aseprite-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 +61 -0
- package/build/aseprite/cli.js +15 -0
- package/build/aseprite/env.js +120 -0
- package/build/aseprite/path.js +33 -0
- package/build/aseprite/steam.js +96 -0
- package/build/index.js +39 -0
- package/build/localtest.js +16 -0
- package/build/server.js +177 -0
- package/build/tools.js +145 -0
- package/build/util.js +28 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Aseprite-MCP
|
|
2
|
+
This server automates Aseprite workflows using the Model Context Protocol (MCP).
|
|
3
|
+
It enables AI, chat assistants, and automation pipelines to directly execute Aseprite tasks such as sprite sheet export, frame extraction, and metadata output.
|
|
4
|
+
*Lua-based automation and high-level sprite/tile generation features are not included yet.
|
|
5
|
+
*Aseprite must be installed in order to use this MCP server.
|
|
6
|
+
|
|
7
|
+
## How to use
|
|
8
|
+
1) Run directly with npx
|
|
9
|
+
```bash
|
|
10
|
+
npx -y aseprite-mcp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
2) Local Build & Run (for development)
|
|
14
|
+
```bash
|
|
15
|
+
npm install
|
|
16
|
+
npm run build
|
|
17
|
+
npx aseprite-mcp
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Using with ChatGPT
|
|
21
|
+
Add the following to your mcp.json
|
|
22
|
+
```json
|
|
23
|
+
{
|
|
24
|
+
"servers": {
|
|
25
|
+
"aseprite-mcp": {
|
|
26
|
+
"command": "npx",
|
|
27
|
+
"args": ["-y", "aseprite-mcp"]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Using with Claude
|
|
34
|
+
Add the following to your servers.json
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"aseprite-mcp": {
|
|
38
|
+
"command": "npx",
|
|
39
|
+
"args": ["-y", "aseprite-mcp"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Using with Cursor
|
|
45
|
+
Add the following to your .cursor.json
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"aseprite-mcp": {
|
|
50
|
+
"command": "npx",
|
|
51
|
+
"args": ["-y", "aseprite-mcp"]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Tools
|
|
58
|
+
- `aseprite_check_environment`: Checks Aseprite installation status, executable path, and version
|
|
59
|
+
- `aseprite_export_sheet`: Exports a sprite sheet as PNG + JSON
|
|
60
|
+
- `aseprite_export_frames`: Exports each animation frame as an individual PNG file
|
|
61
|
+
- `aseprite_export_metadata`: Exports Aseprite metadata in JSON format
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { resolveAsepritePath } from "./env.js";
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
export async function runAsepriteCommand(args) {
|
|
6
|
+
const path = await resolveAsepritePath();
|
|
7
|
+
const command = `"${path}" ${args.join(" ")}`;
|
|
8
|
+
try {
|
|
9
|
+
const { stdout, stderr } = await execAsync(command);
|
|
10
|
+
return { command, stdout, stderr };
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
throw new Error(`Failed to run Aseprite command: ${command}\n${error instanceof Error ? error.message : String(error)}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { exec } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import * as os from "node:os";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
const candidates = {
|
|
8
|
+
win32: [
|
|
9
|
+
"C:\\Program Files\\Aseprite\\aseprite.exe",
|
|
10
|
+
"C:\\Program Files (x86)\\Aseprite\\aseprite.exe",
|
|
11
|
+
],
|
|
12
|
+
darwin: [
|
|
13
|
+
"/Applications/Aseprite.app/Contents/MacOS/aseprite",
|
|
14
|
+
"/usr/local/bin/aseprite",
|
|
15
|
+
"/opt/homebrew/bin/aseprite",
|
|
16
|
+
],
|
|
17
|
+
linux: [
|
|
18
|
+
"/usr/bin/aseprite",
|
|
19
|
+
"/usr/local/bin/aseprite",
|
|
20
|
+
]
|
|
21
|
+
};
|
|
22
|
+
function getSteamVdfPaths() {
|
|
23
|
+
switch (process.platform) {
|
|
24
|
+
case "win32":
|
|
25
|
+
return [
|
|
26
|
+
path.join(process.env.PROGRAMFILESX86 ??
|
|
27
|
+
"C:\\Program Files (x86)", "Steam/steamapps/libraryfolders.vdf"),
|
|
28
|
+
path.join(process.env.PROGRAMFILES ??
|
|
29
|
+
"C:\\Program Files", "Steam/steamapps/libraryfolders.vdf")
|
|
30
|
+
];
|
|
31
|
+
case "darwin":
|
|
32
|
+
return [
|
|
33
|
+
path.join(os.homedir(), "Library/Application Support/Steam/steamapps/libraryfolders.vdf")
|
|
34
|
+
];
|
|
35
|
+
default:
|
|
36
|
+
// linux
|
|
37
|
+
return [
|
|
38
|
+
path.join(os.homedir(), ".steam/steam/steamapps/libraryfolders.vdf"),
|
|
39
|
+
path.join(os.homedir(), ".local/share/Steam/steamapps/libraryfolders.vdf")
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function parseSteamLibraries(vdfContent) {
|
|
44
|
+
const lines = vdfContent.split("\n");
|
|
45
|
+
const paths = [];
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
const match = line.match(/"(\d+)"\s+"(.+?)"/);
|
|
48
|
+
if (match) {
|
|
49
|
+
let p = match[2];
|
|
50
|
+
if (process.platform === "win32") {
|
|
51
|
+
p = p.replace(/\\\\/g, "\\");
|
|
52
|
+
}
|
|
53
|
+
paths.push(p);
|
|
54
|
+
}
|
|
55
|
+
// libraryfolders 2.0
|
|
56
|
+
const kvMatch = line.match(/"path"\s+"(.+?)"/);
|
|
57
|
+
if (kvMatch) {
|
|
58
|
+
let p = kvMatch[1];
|
|
59
|
+
if (process.platform === "win32") {
|
|
60
|
+
p = p.replace(/\\\\/g, "\\");
|
|
61
|
+
}
|
|
62
|
+
paths.push(p);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return Array.from(new Set(paths));
|
|
66
|
+
}
|
|
67
|
+
export function getSteamAsepritePaths() {
|
|
68
|
+
const files = getSteamVdfPaths();
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
if (!existsSync(file))
|
|
71
|
+
continue;
|
|
72
|
+
try {
|
|
73
|
+
const content = readFileSync(file, "utf8");
|
|
74
|
+
const libs = parseSteamLibraries(content);
|
|
75
|
+
const paths = [];
|
|
76
|
+
for (const lib of libs) {
|
|
77
|
+
switch (process.platform) {
|
|
78
|
+
case "win32":
|
|
79
|
+
paths.push(path.join(lib, "steamapps/common/Aseprite/aseprite.exe"));
|
|
80
|
+
break;
|
|
81
|
+
case "darwin":
|
|
82
|
+
paths.push(path.join(lib, "steamapps/common/Aseprite/Aseprite.app/Contents/MacOS/aseprite"));
|
|
83
|
+
break;
|
|
84
|
+
default: // linux
|
|
85
|
+
paths.push(path.join(lib, "steamapps/common/Aseprite/aseprite"));
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return paths;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
export async function resolveAsepritePath() {
|
|
98
|
+
try {
|
|
99
|
+
const platform = process.platform;
|
|
100
|
+
const cmd = platform === "win32" ? "where aseprite" : "which aseprite";
|
|
101
|
+
const { stdout } = await execAsync(cmd);
|
|
102
|
+
const found = stdout.split("\n")[0].trim();
|
|
103
|
+
if (found && existsSync(found))
|
|
104
|
+
return found;
|
|
105
|
+
}
|
|
106
|
+
catch { }
|
|
107
|
+
const defaults = candidates[process.platform] ??
|
|
108
|
+
[];
|
|
109
|
+
for (const path of defaults) {
|
|
110
|
+
if (existsSync(path))
|
|
111
|
+
return path;
|
|
112
|
+
}
|
|
113
|
+
const steamPaths = getSteamAsepritePaths();
|
|
114
|
+
for (const path of steamPaths) {
|
|
115
|
+
if (existsSync(path)) {
|
|
116
|
+
return path;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
throw new Error(`Aseprite not found. Please install it or add to PATH.\nDetected OS: ${process.platform}`);
|
|
120
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function normalizePath(p) {
|
|
4
|
+
if (!p || typeof p !== "string") {
|
|
5
|
+
throw new Error("Path must be a non-empty string");
|
|
6
|
+
}
|
|
7
|
+
return path.resolve(p);
|
|
8
|
+
}
|
|
9
|
+
export function mkdirSafe(dir) {
|
|
10
|
+
try {
|
|
11
|
+
mkdirSync(dir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
throw new Error(`Failed to create directory: ${dir}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function ensureSafePath(p, options = {}) {
|
|
18
|
+
const absPath = normalizePath(p);
|
|
19
|
+
const parentDir = path.dirname(absPath);
|
|
20
|
+
if (options.mustExist) {
|
|
21
|
+
if (!existsSync(absPath)) {
|
|
22
|
+
throw new Error(`File does not exist: ${absPath}`);
|
|
23
|
+
}
|
|
24
|
+
const stat = statSync(absPath);
|
|
25
|
+
if (!stat.isFile()) {
|
|
26
|
+
throw new Error(`Not a file: ${absPath}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (options.createDirIfNeeded) {
|
|
30
|
+
mkdirSafe(parentDir);
|
|
31
|
+
}
|
|
32
|
+
return absPath;
|
|
33
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { exec } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import * as os from "node:os";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
const defaultCandidates = {
|
|
8
|
+
win32: [
|
|
9
|
+
"C:\\Program Files\\Aseprite\\aseprite.exe",
|
|
10
|
+
"C:\\Program Files (x86)\\Aseprite\\aseprite.exe"
|
|
11
|
+
],
|
|
12
|
+
darwin: [
|
|
13
|
+
"/Applications/Aseprite.app/Contents/MacOS/aseprite",
|
|
14
|
+
"/usr/local/bin/aseprite",
|
|
15
|
+
"/opt/homebrew/bin/aseprite"
|
|
16
|
+
],
|
|
17
|
+
linux: [
|
|
18
|
+
"/usr/bin/aseprite",
|
|
19
|
+
"/usr/local/bin/aseprite"
|
|
20
|
+
]
|
|
21
|
+
};
|
|
22
|
+
function getSteamVdfPaths() {
|
|
23
|
+
switch (process.platform) {
|
|
24
|
+
case "win32":
|
|
25
|
+
return [
|
|
26
|
+
path.join(process.env.PROGRAMFILESX86 ??
|
|
27
|
+
"C:\\Program Files (x86)", "Steam/steamapps/libraryfolders.vdf"),
|
|
28
|
+
path.join(process.env.PROGRAMFILES ??
|
|
29
|
+
"C:\\Program Files", "Steam/steamapps/libraryfolders.vdf")
|
|
30
|
+
];
|
|
31
|
+
case "darwin":
|
|
32
|
+
return [
|
|
33
|
+
path.join(os.homedir(), "Library/Application Support/Steam/steamapps/libraryfolders.vdf")
|
|
34
|
+
];
|
|
35
|
+
default:
|
|
36
|
+
// linux
|
|
37
|
+
return [
|
|
38
|
+
path.join(os.homedir(), ".steam/steam/steamapps/libraryfolders.vdf"),
|
|
39
|
+
path.join(os.homedir(), ".local/share/Steam/steamapps/libraryfolders.vdf")
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function parseSteamLibraries(vdfContent) {
|
|
44
|
+
const lines = vdfContent.split("\n");
|
|
45
|
+
const paths = [];
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
const match = line.match(/"(\d+)"\s+"(.+?)"/);
|
|
48
|
+
if (match) {
|
|
49
|
+
let p = match[2];
|
|
50
|
+
if (process.platform === "win32") {
|
|
51
|
+
p = p.replace(/\\\\/g, "\\");
|
|
52
|
+
}
|
|
53
|
+
paths.push(p);
|
|
54
|
+
}
|
|
55
|
+
// libraryfolders 2.0
|
|
56
|
+
const kvMatch = line.match(/"path"\s+"(.+?)"/);
|
|
57
|
+
if (kvMatch) {
|
|
58
|
+
let p = kvMatch[1];
|
|
59
|
+
if (process.platform === "win32") {
|
|
60
|
+
p = p.replace(/\\\\/g, "\\");
|
|
61
|
+
}
|
|
62
|
+
paths.push(p);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return Array.from(new Set(paths));
|
|
66
|
+
}
|
|
67
|
+
export function getSteamAsepritePaths() {
|
|
68
|
+
const files = getSteamVdfPaths();
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
if (!existsSync(file))
|
|
71
|
+
continue;
|
|
72
|
+
try {
|
|
73
|
+
const content = readFileSync(file, "utf8");
|
|
74
|
+
const libs = parseSteamLibraries(content);
|
|
75
|
+
const paths = [];
|
|
76
|
+
for (const lib of libs) {
|
|
77
|
+
switch (process.platform) {
|
|
78
|
+
case "win32":
|
|
79
|
+
paths.push(path.join(lib, "steamapps/common/Aseprite/aseprite.exe"));
|
|
80
|
+
break;
|
|
81
|
+
case "darwin":
|
|
82
|
+
paths.push(path.join(lib, "steamapps/common/Aseprite/Aseprite.app/Contents/MacOS/aseprite"));
|
|
83
|
+
break;
|
|
84
|
+
default: // linux
|
|
85
|
+
paths.push(path.join(lib, "steamapps/common/Aseprite/aseprite"));
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return paths;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return [];
|
|
96
|
+
}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { createToolHandlers, createToolSchemas } from "./tools.js";
|
|
5
|
+
const server = new McpServer({
|
|
6
|
+
name: "aseprite-mcp",
|
|
7
|
+
version: "0.1.0"
|
|
8
|
+
});
|
|
9
|
+
const toolSchemas = createToolSchemas();
|
|
10
|
+
const toolHandlers = createToolHandlers();
|
|
11
|
+
server.registerTool("aseprite_check_environment", {
|
|
12
|
+
description: "Check the environment of Aseprite",
|
|
13
|
+
inputSchema: undefined,
|
|
14
|
+
outputSchema: undefined,
|
|
15
|
+
}, toolHandlers.aseprite_check_environment);
|
|
16
|
+
server.registerTool("aseprite_export_sheet", {
|
|
17
|
+
description: "Export Aseprite file to sprite sheet image",
|
|
18
|
+
inputSchema: toolSchemas.aseprite_export_sheet,
|
|
19
|
+
outputSchema: toolSchemas.aseprite_output_result,
|
|
20
|
+
}, toolHandlers.aseprite_export_sheet);
|
|
21
|
+
server.registerTool("aseprite_export_frames", {
|
|
22
|
+
description: "Export each frame of Aseprite file",
|
|
23
|
+
inputSchema: toolSchemas.aseprite_export_frames,
|
|
24
|
+
outputSchema: toolSchemas.aseprite_output_result,
|
|
25
|
+
}, toolHandlers.aseprite_export_frames);
|
|
26
|
+
server.registerTool("aseprite_export_metadata", {
|
|
27
|
+
description: "Export metadata json from Aseprite file",
|
|
28
|
+
inputSchema: toolSchemas.aseprite_export_metadata,
|
|
29
|
+
outputSchema: toolSchemas.aseprite_output_result,
|
|
30
|
+
}, toolHandlers.aseprite_export_metadata);
|
|
31
|
+
async function main() {
|
|
32
|
+
const transport = new StdioServerTransport();
|
|
33
|
+
await server.connect(transport);
|
|
34
|
+
console.log("Aseprite MCP server started");
|
|
35
|
+
}
|
|
36
|
+
main().catch(err => {
|
|
37
|
+
console.error("MCP Error:", err);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { resolveAsepritePath } from "./aseprite/env.js";
|
|
2
|
+
import { runAsepriteCommand } from "./aseprite/cli.js";
|
|
3
|
+
async function main() {
|
|
4
|
+
try {
|
|
5
|
+
const path = await resolveAsepritePath();
|
|
6
|
+
const { stdout: version } = await runAsepriteCommand(["--version"]);
|
|
7
|
+
console.log("✅ Aseprite OK");
|
|
8
|
+
console.log("Path:", path);
|
|
9
|
+
console.log("Version:", version);
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
console.error("❌ Failed");
|
|
13
|
+
console.error(err.message ?? err);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
main();
|
package/build/server.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import * as z from "zod/v4";
|
|
3
|
+
import { readFileSync as readFileSyncNode } from "node:fs";
|
|
4
|
+
import { resolveAsepritePath as resolveAsepritePathDefault } from "./aseprite/env.js";
|
|
5
|
+
import { runAsepriteCommand as runAsepriteCommandDefault } from "./aseprite/cli.js";
|
|
6
|
+
import { ensureSafePath as ensureSafePathDefault } from "./aseprite/path.js";
|
|
7
|
+
import { errorResult as errorResultDefault, successResult as successResultDefault } from "./util.js";
|
|
8
|
+
export function createServer(deps = {}) {
|
|
9
|
+
const d = {
|
|
10
|
+
resolveAsepritePath: resolveAsepritePathDefault,
|
|
11
|
+
runAsepriteCommand: runAsepriteCommandDefault,
|
|
12
|
+
ensureSafePath: ensureSafePathDefault,
|
|
13
|
+
readFileSync: readFileSyncNode,
|
|
14
|
+
successResult: successResultDefault,
|
|
15
|
+
errorResult: errorResultDefault,
|
|
16
|
+
...deps,
|
|
17
|
+
};
|
|
18
|
+
const server = new McpServer({
|
|
19
|
+
name: "aseprite-mcp",
|
|
20
|
+
version: "0.1.0",
|
|
21
|
+
});
|
|
22
|
+
const tools = new Map();
|
|
23
|
+
tools.set("aseprite_check_environment", server.registerTool("aseprite_check_environment", {
|
|
24
|
+
description: "Check the environment of Aseprite",
|
|
25
|
+
inputSchema: undefined,
|
|
26
|
+
outputSchema: undefined,
|
|
27
|
+
}, async () => {
|
|
28
|
+
try {
|
|
29
|
+
const asepritePath = await d.resolveAsepritePath();
|
|
30
|
+
const { stdout: version } = await d.runAsepriteCommand(["--version"]);
|
|
31
|
+
return d.successResult("aseprite_check_environment", {
|
|
32
|
+
path: asepritePath,
|
|
33
|
+
version,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
return d.errorResult("aseprite_check_environment", e instanceof Error ? e.message : String(e));
|
|
38
|
+
}
|
|
39
|
+
}));
|
|
40
|
+
tools.set("aseprite_export_sheet", server.registerTool("aseprite_export_sheet", {
|
|
41
|
+
description: "Export Aseprite file to sprite sheet image",
|
|
42
|
+
inputSchema: z.object({
|
|
43
|
+
inputFile: z.string(),
|
|
44
|
+
outputSheet: z.string(),
|
|
45
|
+
sheetType: z.enum(["rows", "columns", "packed"]).optional().default("packed"),
|
|
46
|
+
dataFile: z.string().optional(),
|
|
47
|
+
tag: z.string().optional(),
|
|
48
|
+
}),
|
|
49
|
+
outputSchema: z.object({
|
|
50
|
+
content: z.array(z.object({
|
|
51
|
+
type: z.literal("text"),
|
|
52
|
+
text: z.string(),
|
|
53
|
+
})),
|
|
54
|
+
}),
|
|
55
|
+
}, async ({ inputFile, outputSheet, sheetType, dataFile, tag }) => {
|
|
56
|
+
try {
|
|
57
|
+
const inputAbsPath = d.ensureSafePath(inputFile, { mustExist: true });
|
|
58
|
+
const sheetAbsPath = d.ensureSafePath(outputSheet, { createDirIfNeeded: true });
|
|
59
|
+
const dataAbsPath = dataFile
|
|
60
|
+
? d.ensureSafePath(dataFile, { createDirIfNeeded: true })
|
|
61
|
+
: undefined;
|
|
62
|
+
const args = [
|
|
63
|
+
"--batch",
|
|
64
|
+
`"${inputAbsPath}"`,
|
|
65
|
+
"--sheet",
|
|
66
|
+
`"${sheetAbsPath}"`,
|
|
67
|
+
"--sheet-type",
|
|
68
|
+
sheetType,
|
|
69
|
+
];
|
|
70
|
+
if (tag)
|
|
71
|
+
args.push("--tag", `"${tag}"`);
|
|
72
|
+
if (dataAbsPath)
|
|
73
|
+
args.push("--data", `"${dataAbsPath}"`);
|
|
74
|
+
const result = await d.runAsepriteCommand(args);
|
|
75
|
+
return d.successResult("aseprite_export_sheet", {
|
|
76
|
+
command: result.command,
|
|
77
|
+
inputFile: inputAbsPath,
|
|
78
|
+
outputSheet: sheetAbsPath,
|
|
79
|
+
sheetType,
|
|
80
|
+
dataFile: dataAbsPath ? dataAbsPath : undefined,
|
|
81
|
+
tag: tag ? tag : undefined,
|
|
82
|
+
stdout: result.stdout.trim(),
|
|
83
|
+
stderr: result.stderr.trim(),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
return d.errorResult("aseprite_export_sheet", e instanceof Error ? e.message : String(e));
|
|
88
|
+
}
|
|
89
|
+
}));
|
|
90
|
+
tools.set("aseprite_export_frames", server.registerTool("aseprite_export_frames", {
|
|
91
|
+
description: "Export each frame of Aseprite file",
|
|
92
|
+
inputSchema: z.object({
|
|
93
|
+
inputFile: z.string(),
|
|
94
|
+
outputPattern: z.string(),
|
|
95
|
+
tag: z.string().optional(),
|
|
96
|
+
}),
|
|
97
|
+
outputSchema: z.object({
|
|
98
|
+
content: z.array(z.object({
|
|
99
|
+
type: z.literal("text"),
|
|
100
|
+
text: z.string(),
|
|
101
|
+
})),
|
|
102
|
+
}),
|
|
103
|
+
}, async ({ inputFile, outputPattern, tag }) => {
|
|
104
|
+
try {
|
|
105
|
+
const inputAbsPath = d.ensureSafePath(inputFile, { mustExist: true });
|
|
106
|
+
const outputAbsPath = d.ensureSafePath(outputPattern, { createDirIfNeeded: true });
|
|
107
|
+
const args = [
|
|
108
|
+
"--batch",
|
|
109
|
+
`"${inputAbsPath}"`,
|
|
110
|
+
"--save-as",
|
|
111
|
+
`"${outputAbsPath}"`,
|
|
112
|
+
];
|
|
113
|
+
if (tag)
|
|
114
|
+
args.push("--tag", `"${tag}"`);
|
|
115
|
+
const result = await d.runAsepriteCommand(args);
|
|
116
|
+
return d.successResult("aseprite_export_frames", {
|
|
117
|
+
command: result.command,
|
|
118
|
+
inputFile: inputAbsPath,
|
|
119
|
+
outputPattern: outputAbsPath,
|
|
120
|
+
tag: tag ? tag : undefined,
|
|
121
|
+
stdout: result.stdout.trim(),
|
|
122
|
+
stderr: result.stderr.trim(),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
return d.errorResult("aseprite_export_frames", e instanceof Error ? e.message : String(e));
|
|
127
|
+
}
|
|
128
|
+
}));
|
|
129
|
+
tools.set("aseprite_export_metadata", server.registerTool("aseprite_export_metadata", {
|
|
130
|
+
description: "Export metadata json from Aseprite file",
|
|
131
|
+
inputSchema: z.object({
|
|
132
|
+
inputFile: z.string(),
|
|
133
|
+
dataFile: z.string(),
|
|
134
|
+
format: z.string().optional(),
|
|
135
|
+
}),
|
|
136
|
+
outputSchema: z.object({
|
|
137
|
+
content: z.array(z.object({
|
|
138
|
+
type: z.literal("text"),
|
|
139
|
+
text: z.string(),
|
|
140
|
+
})),
|
|
141
|
+
}),
|
|
142
|
+
}, async ({ inputFile, dataFile, format }) => {
|
|
143
|
+
try {
|
|
144
|
+
const inputAbsPath = d.ensureSafePath(inputFile, { mustExist: true });
|
|
145
|
+
const dataAbsPath = d.ensureSafePath(dataFile, { createDirIfNeeded: true });
|
|
146
|
+
const args = [
|
|
147
|
+
"--batch",
|
|
148
|
+
`"${inputAbsPath}"`,
|
|
149
|
+
"--data",
|
|
150
|
+
`"${dataAbsPath}"`,
|
|
151
|
+
];
|
|
152
|
+
if (format)
|
|
153
|
+
args.push("--format", `"${format}"`);
|
|
154
|
+
const result = await d.runAsepriteCommand(args);
|
|
155
|
+
let metaText = "";
|
|
156
|
+
try {
|
|
157
|
+
metaText = d.readFileSync(dataAbsPath, "utf8");
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
metaText = `Failed to read metadata: ${e instanceof Error ? e.message : String(e)}`;
|
|
161
|
+
}
|
|
162
|
+
return d.successResult("aseprite_export_metadata", {
|
|
163
|
+
command: result.command,
|
|
164
|
+
inputFile: inputAbsPath,
|
|
165
|
+
dataFile: dataAbsPath,
|
|
166
|
+
format: format ? format : undefined,
|
|
167
|
+
stdout: result.stdout.trim(),
|
|
168
|
+
stderr: result.stderr.trim(),
|
|
169
|
+
metadata: metaText,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
catch (e) {
|
|
173
|
+
return d.errorResult("aseprite_export_metadata", e instanceof Error ? e.message : String(e));
|
|
174
|
+
}
|
|
175
|
+
}));
|
|
176
|
+
return { server, tools };
|
|
177
|
+
}
|
package/build/tools.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import { resolveAsepritePath } from "./aseprite/env.js";
|
|
3
|
+
import { runAsepriteCommand } from "./aseprite/cli.js";
|
|
4
|
+
import { errorResult, successResult } from "./util.js";
|
|
5
|
+
import { ensureSafePath } from "./aseprite/path.js";
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
const toolSchemas = createToolSchemas();
|
|
8
|
+
export function createToolHandlers() {
|
|
9
|
+
const aseprite_check_environment = async () => {
|
|
10
|
+
try {
|
|
11
|
+
const asepritePath = await resolveAsepritePath();
|
|
12
|
+
const { stdout: version } = await runAsepriteCommand(["--version"]);
|
|
13
|
+
return successResult("aseprite_check_environment", { path: asepritePath, version });
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
return errorResult("aseprite_check_environment", e instanceof Error ? e.message : String(e));
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const aseprite_export_sheet = async ({ inputFile, outputSheet, sheetType = "packed", dataFile, tag }) => {
|
|
20
|
+
try {
|
|
21
|
+
const inputAbsPath = ensureSafePath(inputFile, { mustExist: true });
|
|
22
|
+
const sheetAbsPath = ensureSafePath(outputSheet, { createDirIfNeeded: true });
|
|
23
|
+
const dataAbsPath = dataFile ? ensureSafePath(dataFile, { createDirIfNeeded: true }) : undefined;
|
|
24
|
+
const args = [
|
|
25
|
+
"--batch",
|
|
26
|
+
`"${inputAbsPath}"`,
|
|
27
|
+
"--sheet",
|
|
28
|
+
`"${sheetAbsPath}"`,
|
|
29
|
+
"--sheet-type",
|
|
30
|
+
sheetType
|
|
31
|
+
];
|
|
32
|
+
if (tag)
|
|
33
|
+
args.push("--tag", `"${tag}"`);
|
|
34
|
+
if (dataAbsPath)
|
|
35
|
+
args.push("--data", `"${dataAbsPath}"`);
|
|
36
|
+
const result = await runAsepriteCommand(args);
|
|
37
|
+
return successResult("aseprite_export_sheet", {
|
|
38
|
+
command: result.command,
|
|
39
|
+
inputFile: inputAbsPath,
|
|
40
|
+
outputSheet: sheetAbsPath,
|
|
41
|
+
sheetType,
|
|
42
|
+
dataFile: dataAbsPath ? dataAbsPath : undefined,
|
|
43
|
+
tag: tag ? tag : undefined,
|
|
44
|
+
stdout: result.stdout.trim(),
|
|
45
|
+
stderr: result.stderr.trim(),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
return errorResult("aseprite_export_sheet", e instanceof Error ? e.message : String(e));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const aseprite_export_frames = async ({ inputFile, outputPattern, tag }) => {
|
|
53
|
+
try {
|
|
54
|
+
const inputAbsPath = ensureSafePath(inputFile, { mustExist: true });
|
|
55
|
+
const outputAbsPath = ensureSafePath(outputPattern, { createDirIfNeeded: true });
|
|
56
|
+
const args = [
|
|
57
|
+
"--batch",
|
|
58
|
+
`"${inputAbsPath}"`,
|
|
59
|
+
"--save-as",
|
|
60
|
+
`"${outputAbsPath}"`
|
|
61
|
+
];
|
|
62
|
+
if (tag)
|
|
63
|
+
args.push("--tag", `"${tag}"`);
|
|
64
|
+
const result = await runAsepriteCommand(args);
|
|
65
|
+
return successResult("aseprite_export_frames", {
|
|
66
|
+
command: result.command,
|
|
67
|
+
inputFile: inputAbsPath,
|
|
68
|
+
outputPattern: outputAbsPath,
|
|
69
|
+
tag: tag ? tag : undefined,
|
|
70
|
+
stdout: result.stdout.trim(),
|
|
71
|
+
stderr: result.stderr.trim(),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
return errorResult("aseprite_export_frames", e instanceof Error ? e.message : String(e));
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const aseprite_export_metadata = async ({ inputFile, dataFile, format }) => {
|
|
79
|
+
try {
|
|
80
|
+
const inputAbsPath = ensureSafePath(inputFile, { mustExist: true });
|
|
81
|
+
const dataAbsPath = ensureSafePath(dataFile, { createDirIfNeeded: true });
|
|
82
|
+
const args = [
|
|
83
|
+
"--batch",
|
|
84
|
+
`"${inputAbsPath}"`,
|
|
85
|
+
"--data",
|
|
86
|
+
`"${dataAbsPath}"`
|
|
87
|
+
];
|
|
88
|
+
if (format)
|
|
89
|
+
args.push("--format", `"${format}"`);
|
|
90
|
+
const result = await runAsepriteCommand(args);
|
|
91
|
+
let metaText = "";
|
|
92
|
+
try {
|
|
93
|
+
metaText = readFileSync(dataAbsPath, "utf8");
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
metaText = `Failed to read metadata: ${e instanceof Error ? e.message : String(e)}`;
|
|
97
|
+
}
|
|
98
|
+
return successResult("aseprite_export_metadata", {
|
|
99
|
+
command: result.command,
|
|
100
|
+
inputFile: inputAbsPath,
|
|
101
|
+
dataFile: dataAbsPath,
|
|
102
|
+
format: format ? format : undefined,
|
|
103
|
+
stdout: result.stdout.trim(),
|
|
104
|
+
stderr: result.stderr.trim(),
|
|
105
|
+
metadata: metaText,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
return errorResult("aseprite_export_metadata", e instanceof Error ? e.message : String(e));
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
aseprite_check_environment,
|
|
114
|
+
aseprite_export_sheet,
|
|
115
|
+
aseprite_export_frames,
|
|
116
|
+
aseprite_export_metadata
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export function createToolSchemas() {
|
|
120
|
+
return {
|
|
121
|
+
aseprite_export_sheet: z.object({
|
|
122
|
+
inputFile: z.string(),
|
|
123
|
+
outputSheet: z.string(),
|
|
124
|
+
sheetType: z.enum(["rows", "columns", "packed"]).optional().default("packed"),
|
|
125
|
+
dataFile: z.string().optional(),
|
|
126
|
+
tag: z.string().optional(),
|
|
127
|
+
}),
|
|
128
|
+
aseprite_export_frames: z.object({
|
|
129
|
+
inputFile: z.string(),
|
|
130
|
+
outputPattern: z.string(),
|
|
131
|
+
tag: z.string().optional(),
|
|
132
|
+
}),
|
|
133
|
+
aseprite_export_metadata: z.object({
|
|
134
|
+
inputFile: z.string(),
|
|
135
|
+
dataFile: z.string(),
|
|
136
|
+
format: z.string().optional(),
|
|
137
|
+
}),
|
|
138
|
+
aseprite_output_result: z.object({
|
|
139
|
+
content: z.array(z.object({
|
|
140
|
+
type: z.literal("text"),
|
|
141
|
+
text: z.string(),
|
|
142
|
+
})),
|
|
143
|
+
}),
|
|
144
|
+
};
|
|
145
|
+
}
|
package/build/util.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function successResult(tool, result) {
|
|
2
|
+
return {
|
|
3
|
+
content: [
|
|
4
|
+
{
|
|
5
|
+
type: "text",
|
|
6
|
+
text: JSON.stringify({
|
|
7
|
+
success: true,
|
|
8
|
+
tool,
|
|
9
|
+
result,
|
|
10
|
+
}, null, 2)
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function errorResult(tool, error) {
|
|
16
|
+
return {
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: JSON.stringify({
|
|
21
|
+
success: false,
|
|
22
|
+
tool,
|
|
23
|
+
error,
|
|
24
|
+
}, null, 2)
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
};
|
|
28
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@iborymagic/aseprite-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for using Aseprite API",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"aseprite-mcp": "./build/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"build"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"start": "node build/index.js",
|
|
16
|
+
"dev": "npm run build && npm run start",
|
|
17
|
+
"test": "jest --runInBand --slient=false"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
21
|
+
"zod": "^4.2.1"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/jest": "^30.0.0",
|
|
25
|
+
"@types/node": "^25.0.0",
|
|
26
|
+
"jest": "^30.2.0",
|
|
27
|
+
"ts-jest": "^29.4.6",
|
|
28
|
+
"typescript": "^5.0.0"
|
|
29
|
+
},
|
|
30
|
+
"author": "iborymagic",
|
|
31
|
+
"license": "MIT"
|
|
32
|
+
}
|