@iborymagic/aseprite-mcp 0.1.0 → 0.2.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 CHANGED
@@ -1,9 +1,32 @@
1
1
  # Aseprite-MCP
2
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.
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
+
4
5
  *Lua-based automation and high-level sprite/tile generation features are not included yet.
5
6
  *Aseprite must be installed in order to use this MCP server.
6
7
 
8
+ ## Features Overview
9
+ ### V1 - Export/Utility
10
+ Supports fundamental Aseprite export workflow:
11
+ - `aseprite_check_environment`: Checks Aseprite installation status, executable path, and version
12
+ - `aseprite_export_sheet`: Exports a sprite sheet as PNG + JSON
13
+ - `aseprite_export_frames`: Exports each animation frame as an individual PNG file
14
+ - `aseprite_export_metadata`: Exports Aseprite metadata in JSON format
15
+
16
+ ### V2 - Lua Automation
17
+ Adds deeper control using Aseprite Lua scripting, enabling safe AI-driven editing operations such as:
18
+ - `aseprite_list_lua_templates`: Lists available Lua templates
19
+ - `aseprite_run_lua_template`: Runs a predefined safe Lua automation(templates)
20
+ - `remove_layer_by_name`: Removing specific layers
21
+ - `export_tag_frames`: Palette recoloring
22
+ - `recolor_palette`: Normalizing animation speed
23
+ - `normalize_animation_speed`: Exporting only specific animation tags
24
+ - `auto_crop_transparent`: Removing empty transparent borders around the sprite
25
+ - `export_layer_only`: Exporting only a specific layer as a flattened PNG image
26
+ - `export_tag_frames`: Exporting all frames within a specific animation tag as individual PNG files
27
+ - `merge_visible_layers`: Merging all currently visible layers into a single layer
28
+ - `aseprite_run_lua`: Executes a raw Lua script (advanced / unsafe)
29
+
7
30
  ## How to use
8
31
  1) Run directly with npx
9
32
  ```bash
@@ -53,9 +76,3 @@ Add the following to your .cursor.json
53
76
  }
54
77
  }
55
78
  ```
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
@@ -33,7 +33,7 @@ function getSteamVdfPaths() {
33
33
  path.join(os.homedir(), "Library/Application Support/Steam/steamapps/libraryfolders.vdf")
34
34
  ];
35
35
  default:
36
- // linux
36
+ // Linux
37
37
  return [
38
38
  path.join(os.homedir(), ".steam/steam/steamapps/libraryfolders.vdf"),
39
39
  path.join(os.homedir(), ".local/share/Steam/steamapps/libraryfolders.vdf")
@@ -81,7 +81,8 @@ export function getSteamAsepritePaths() {
81
81
  case "darwin":
82
82
  paths.push(path.join(lib, "steamapps/common/Aseprite/Aseprite.app/Contents/MacOS/aseprite"));
83
83
  break;
84
- default: // linux
84
+ default:
85
+ // Linux
85
86
  paths.push(path.join(lib, "steamapps/common/Aseprite/aseprite"));
86
87
  break;
87
88
  }
@@ -0,0 +1,145 @@
1
+ import z from "zod";
2
+ import { resolveAsepritePath } from "./env.js";
3
+ import { runAsepriteCommand } from "./cli.js";
4
+ import { errorResult, successResult } from "../util.js";
5
+ import { ensureSafePath } from "./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/index.js CHANGED
@@ -1,33 +1,51 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { createToolHandlers, createToolSchemas } from "./tools.js";
4
+ import { createToolHandlers as createAsepriteToolHandlers, createToolSchemas as createAsepriteToolSchemas } from "./aseprite/tools.js";
5
+ import { createToolHandlers as createLuaToolHandlers, createToolSchemas as createLuaToolSchemas } from "./lua/tools.js";
5
6
  const server = new McpServer({
6
7
  name: "aseprite-mcp",
7
8
  version: "0.1.0"
8
9
  });
9
- const toolSchemas = createToolSchemas();
10
- const toolHandlers = createToolHandlers();
10
+ const asepriteToolSchemas = createAsepriteToolSchemas();
11
+ const luaToolSchemas = createLuaToolSchemas();
12
+ const asepriteToolHandlers = createAsepriteToolHandlers();
13
+ const luaToolHandlers = createLuaToolHandlers();
11
14
  server.registerTool("aseprite_check_environment", {
12
15
  description: "Check the environment of Aseprite",
13
16
  inputSchema: undefined,
14
17
  outputSchema: undefined,
15
- }, toolHandlers.aseprite_check_environment);
18
+ }, asepriteToolHandlers.aseprite_check_environment);
16
19
  server.registerTool("aseprite_export_sheet", {
17
20
  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
+ inputSchema: asepriteToolSchemas.aseprite_export_sheet,
22
+ outputSchema: asepriteToolSchemas.aseprite_output_result,
23
+ }, asepriteToolHandlers.aseprite_export_sheet);
21
24
  server.registerTool("aseprite_export_frames", {
22
25
  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
+ inputSchema: asepriteToolSchemas.aseprite_export_frames,
27
+ outputSchema: asepriteToolSchemas.aseprite_output_result,
28
+ }, asepriteToolHandlers.aseprite_export_frames);
26
29
  server.registerTool("aseprite_export_metadata", {
27
30
  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
+ inputSchema: asepriteToolSchemas.aseprite_export_metadata,
32
+ outputSchema: asepriteToolSchemas.aseprite_output_result,
33
+ }, asepriteToolHandlers.aseprite_export_metadata);
34
+ server.registerTool("aseprite_list_lua_templates", {
35
+ description: "List available Aseprite Lua templates.",
36
+ inputSchema: undefined,
37
+ outputSchema: undefined,
38
+ }, luaToolHandlers.aseprite_list_lua_templates);
39
+ server.registerTool("aseprite_run_lua_template", {
40
+ description: "Run a predefined Aseprite Lua template with parameters.",
41
+ inputSchema: luaToolSchemas.aseprite_run_lua_template,
42
+ outputSchema: luaToolSchemas.lua_output_result,
43
+ }, luaToolHandlers.aseprite_run_lua_template);
44
+ server.registerTool("aseprite_run_lua_script", {
45
+ description: "Run a raw Lua script (advanced / unsafe).",
46
+ inputSchema: luaToolSchemas.aseprite_run_lua_script,
47
+ outputSchema: luaToolSchemas.lua_output_result,
48
+ }, luaToolHandlers.aseprite_run_lua_script);
31
49
  async function main() {
32
50
  const transport = new StdioServerTransport();
33
51
  await server.connect(transport);
@@ -0,0 +1,60 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { ensureSafePath } from "../aseprite/path.js";
4
+ import { runAsepriteCommand } from "../aseprite/cli.js";
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ export const LUA_TEMPLATES = [
8
+ {
9
+ id: "remove_layer_by_name",
10
+ description: "Removes a layer with a given name and saves to a new file (or overwrites).",
11
+ params: ["inputFile", "layerName"],
12
+ optionalParams: ["saveOutput"],
13
+ scriptPath: path.join(__dirname, "templates", "remove_layer_by_name.lua")
14
+ },
15
+ {
16
+ id: "export_tag_frames",
17
+ description: "Exports only the frames of a specific tag as PNG images.",
18
+ params: ["inputFile", "tag", "outputDir"],
19
+ optionalParams: ["filenamePrefix"],
20
+ scriptPath: path.join(__dirname, "templates", "export_tag_frames.lua")
21
+ },
22
+ {
23
+ id: "recolor_palette",
24
+ description: "Recolors the palette based on a mapping of from->to colors.",
25
+ params: ["inputFile", "saveOutput", "mapping"],
26
+ optionalParams: [],
27
+ scriptPath: path.join(__dirname, "templates", "recolor_palette.lua")
28
+ },
29
+ {
30
+ id: "normalize_animation_speed",
31
+ description: "Normalizes all frame durations to a single target duration (in seconds).",
32
+ params: ["inputFile", "saveOutput", "targetDuration"],
33
+ optionalParams: [],
34
+ scriptPath: path.join(__dirname, "templates", "normalize_animation_speed.lua")
35
+ }
36
+ ];
37
+ export function findLuaTemplate(id) {
38
+ return LUA_TEMPLATES.find(t => t.id === id);
39
+ }
40
+ export async function runLuaTemplate(template, params) {
41
+ return runLua(template, params);
42
+ }
43
+ export async function runLuaScript(scriptPath, params) {
44
+ return runLua(scriptPath, params);
45
+ }
46
+ export async function runLua(script, params) {
47
+ const args = ["--batch"];
48
+ if (typeof params.inputFile === "string") {
49
+ const inputAbs = ensureSafePath(params.inputFile, { mustExist: true });
50
+ args.push(`"${inputAbs}"`);
51
+ }
52
+ const scriptPath = typeof script === "string" ? script : script.scriptPath;
53
+ args.push("--script", `"${scriptPath}"`);
54
+ for (const [key, value] of Object.entries(params)) {
55
+ if (key === "inputFile" || value == null)
56
+ continue;
57
+ args.push("--script-param", `${key}=${value}`);
58
+ }
59
+ return runAsepriteCommand(args);
60
+ }
@@ -0,0 +1,93 @@
1
+ import { errorResult, successResult } from "../util.js";
2
+ import { findLuaTemplate, LUA_TEMPLATES, runLuaScript, runLuaTemplate } from "./templates.js";
3
+ import { ensureSafePath } from "../aseprite/path.js";
4
+ import path from "node:path";
5
+ import z from "zod";
6
+ import os from "node:os";
7
+ import fs from "node:fs/promises";
8
+ const toolSchemas = createToolSchemas();
9
+ export function createToolHandlers() {
10
+ const aseprite_list_lua_templates = async () => {
11
+ return successResult("aseprite_list_lua_templates", {
12
+ templates: LUA_TEMPLATES.map(template => ({
13
+ id: template.id,
14
+ description: template.description,
15
+ params: template.params,
16
+ optionalParams: template.optionalParams ?? []
17
+ }))
18
+ });
19
+ };
20
+ const aseprite_run_lua_template = async ({ templateId, params = {} }) => {
21
+ const template = findLuaTemplate(templateId);
22
+ if (!template) {
23
+ return errorResult("aseprite_run_lua_template", new Error(`Unknown templateId: ${templateId}`));
24
+ }
25
+ const missing = template.params.filter(key => !params.hasOwnProperty(key));
26
+ if (missing.length > 0) {
27
+ return errorResult("aseprite_run_lua_template", new Error(`Missing required params: ${missing.join(", ")}`));
28
+ }
29
+ try {
30
+ const result = await runLuaTemplate(template, params);
31
+ return successResult("aseprite_run_lua_template", {
32
+ command: result.command,
33
+ templateId,
34
+ stdout: result.stdout.trim(),
35
+ stderr: result.stderr.trim()
36
+ });
37
+ }
38
+ catch (err) {
39
+ return errorResult("aseprite_run_lua_template", new Error(`Execution failed: ${err instanceof Error ? err.message : String(err)}`));
40
+ }
41
+ };
42
+ const aseprite_run_lua_script = async ({ scriptPath, scriptContent, params = {} }) => {
43
+ try {
44
+ let luaFilePath;
45
+ if (scriptPath) {
46
+ luaFilePath = ensureSafePath(scriptPath, { mustExist: true });
47
+ }
48
+ else {
49
+ const tempPath = path.join(os.tmpdir(), `aseprite-mcp-${Date.now()}.lua`);
50
+ await fs.writeFile(tempPath, String(scriptContent), "utf8");
51
+ luaFilePath = tempPath;
52
+ }
53
+ const result = await runLuaScript(luaFilePath, params);
54
+ return successResult("aseprite_run_lua_script", {
55
+ command: result.command,
56
+ stdout: result.stdout.trim(),
57
+ stderr: result.stderr.trim()
58
+ });
59
+ }
60
+ catch (err) {
61
+ return errorResult("aseprite_run_lua_script", new Error(`Execution failed: ${err instanceof Error ? err.message : String(err)}`));
62
+ }
63
+ };
64
+ return {
65
+ aseprite_list_lua_templates,
66
+ aseprite_run_lua_template,
67
+ aseprite_run_lua_script
68
+ };
69
+ }
70
+ ;
71
+ export function createToolSchemas() {
72
+ return {
73
+ aseprite_run_lua_template: z.object({
74
+ templateId: z.string(),
75
+ params: z.record(z.string(), z.any()).optional()
76
+ }),
77
+ aseprite_run_lua_script: z
78
+ .object({
79
+ scriptPath: z.string().optional(),
80
+ scriptContent: z.string().optional(),
81
+ params: z.record(z.string(), z.any()).optional()
82
+ })
83
+ .refine(v => !!v.scriptPath || !!v.scriptContent, {
84
+ message: "Either scriptPath or scriptContent is required."
85
+ }),
86
+ lua_output_result: z.object({
87
+ content: z.array(z.object({
88
+ type: z.literal("text"),
89
+ text: z.string()
90
+ }))
91
+ })
92
+ };
93
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iborymagic/aseprite-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server for using Aseprite API",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -14,19 +14,24 @@
14
14
  "build": "tsc",
15
15
  "start": "node build/index.js",
16
16
  "dev": "npm run build && npm run start",
17
- "test": "jest --runInBand --slient=false"
17
+ "test": "vitest"
18
18
  },
19
19
  "dependencies": {
20
20
  "@modelcontextprotocol/sdk": "^1.25.1",
21
21
  "zod": "^4.2.1"
22
22
  },
23
23
  "devDependencies": {
24
- "@types/jest": "^30.0.0",
25
24
  "@types/node": "^25.0.0",
26
- "jest": "^30.2.0",
27
- "ts-jest": "^29.4.6",
28
- "typescript": "^5.0.0"
25
+ "typescript": "^5.0.0",
26
+ "vitest": "^4.0.16"
29
27
  },
30
28
  "author": "iborymagic",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/iborymagic/aseprite-mcp"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/iborymagic/aseprite-mcp/issues"
35
+ },
31
36
  "license": "MIT"
32
37
  }