@iborymagic/aseprite-mcp 0.3.4 → 0.3.6

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
@@ -28,14 +28,14 @@ Adds deeper control using Aseprite Lua scripting, enabling safe AI-driven editin
28
28
  ## How to use
29
29
  1) Run directly with npx
30
30
  ```bash
31
- npx -y aseprite-mcp
31
+ npx -y @iborymagic/aseprite-mcp
32
32
  ```
33
33
 
34
34
  2) Local Build & Run (for development)
35
35
  ```bash
36
36
  npm install
37
37
  npm run build
38
- npx aseprite-mcp
38
+ npx @iborymagic/aseprite-mcp
39
39
  ```
40
40
 
41
41
  ### Using with Claude Desktop
@@ -45,7 +45,7 @@ Add the following to your claude_desktop_config.json
45
45
  "mcpServers": {
46
46
  "aseprite-mcp": {
47
47
  "command": "npx",
48
- "args": ["-y", "aseprite-mcp"]
48
+ "args": ["-y", "@iborymagic/aseprite-mcp"]
49
49
  }
50
50
  }
51
51
  }
@@ -58,7 +58,7 @@ Add the following to your mcp.json
58
58
  "mcpServers": {
59
59
  "aseprite-mcp": {
60
60
  "command": "npx",
61
- "args": ["-y", "aseprite-mcp"]
61
+ "args": ["-y", "@iborymagic/aseprite-mcp"]
62
62
  }
63
63
  }
64
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iborymagic/aseprite-mcp",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "MCP server for using Aseprite API",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -1,96 +0,0 @@
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
- }
@@ -1,55 +0,0 @@
1
- import path from "node:path";
2
- import fs from "node:fs/promises";
3
- import axios from "axios";
4
- export class StubImageGenerator {
5
- async generate(params) {
6
- throw new Error(`Image generator not configured. Tried to generate: "${params.prompt}" to ${params.outputPath}`);
7
- }
8
- }
9
- export class StableDiffusionWebuiGenerator {
10
- baseUrl;
11
- constructor(baseUrl) {
12
- this.baseUrl = baseUrl;
13
- }
14
- async generate(params) {
15
- const url = `${this.baseUrl}/sdapi/v1/txt2img`;
16
- const prompt = `${params.prompt}, ` +
17
- `pixel art, retro 16-bit, clean outline, sharp edges, high contrast, game sprite`;
18
- const negative = params.negativePrompt ??
19
- "photo, realistic, blurry, lowres, smear, oil painting";
20
- const body = {
21
- prompt,
22
- negative_prompt: negative,
23
- width: params.width,
24
- height: params.height,
25
- steps: 28,
26
- sampler_name: "DPM++ 2M",
27
- cfg_scale: 7,
28
- seed: params.seed ?? -1,
29
- restore_faces: false,
30
- tiling: false
31
- };
32
- const res = await axios.post(url, body, {
33
- timeout: 1000 * 60 * 3
34
- });
35
- if (!res.data?.images?.length) {
36
- throw new Error("Stable Diffusion returned no images");
37
- }
38
- const imgBase64 = res.data.images[0];
39
- const buffer = Buffer.from(imgBase64, "base64");
40
- const dir = path.dirname(params.outputPath);
41
- await fs.mkdir(dir, { recursive: true });
42
- await fs.writeFile(params.outputPath, buffer);
43
- let seed = params.seed;
44
- try {
45
- const info = JSON.parse(res.data.info ?? "{}");
46
- if (info.seed)
47
- seed = info.seed;
48
- }
49
- catch { }
50
- return {
51
- imagePath: params.outputPath,
52
- seed
53
- };
54
- }
55
- }
@@ -1,210 +0,0 @@
1
- import z from "zod";
2
- import { errorResult, successResult } from "../util.js";
3
- import { ensureSafePath } from "../aseprite/path.js";
4
- import path from "path";
5
- import fs from "node:fs/promises";
6
- import { runLuaScriptFile } from "../lua/cli.js";
7
- import { createToolHandlers as createPipelineToolHandlers } from "../pipeline/tools.js";
8
- import { fileURLToPath } from "node:url";
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = path.dirname(__filename);
11
- const toolSchemas = createToolSchemas();
12
- export function createToolHandlers() {
13
- const character_generate_concept = async ({ params: { prompt, workspaceDir, fileName = "concept.png", width = 256, height = 256, seed, stylePreset = "pixel_art" } }, generator) => {
14
- try {
15
- const workspaceAbs = ensureSafePath(workspaceDir, { mustExist: false });
16
- await fs.mkdir(workspaceAbs, { recursive: true });
17
- const outputPath = path.join(workspaceAbs, fileName);
18
- const result = await generator.generate({
19
- prompt,
20
- negativePrompt: undefined,
21
- width,
22
- height,
23
- seed,
24
- stylePreset,
25
- outputPath
26
- });
27
- return successResult("character_generate_concept", {
28
- prompt,
29
- workspaceDir: workspaceAbs,
30
- imagePath: result.imagePath,
31
- seed: result.seed
32
- });
33
- }
34
- catch (err) {
35
- return errorResult("character_generate_concept", `Character generate concept failed: ${err instanceof Error ? err.message : String(err)}`);
36
- }
37
- };
38
- const character_import_from_concept = async ({ params: { conceptImage, outputFile, spriteSize: spriteSizeParam = 0, animationSpec: animationSpecParam = "Idle:4" }, }) => {
39
- try {
40
- const conceptAbs = ensureSafePath(conceptImage, {
41
- mustExist: true
42
- });
43
- const outputAbs = ensureSafePath(outputFile, {
44
- mustExist: false
45
- });
46
- const spriteSize = spriteSizeParam ?? 0;
47
- const animationSpec = animationSpecParam ?? "Idle:4";
48
- // Lua templates are in the source directory, reference from project root
49
- const luaScriptPath = path.join(process.cwd(), "src", "lua", "templates", "character_import_from_concept.lua");
50
- const result = await runLuaScriptFile(luaScriptPath, {
51
- conceptImage: conceptAbs,
52
- outputFile: outputAbs,
53
- spriteSize,
54
- animationSpec
55
- });
56
- if (result.timedOut) {
57
- return errorResult("character_import_from_concept", `Lua script timed out while importing from concept: ${luaScriptPath}`);
58
- }
59
- return successResult("character_import_from_concept", {
60
- command: result.command,
61
- conceptImage: conceptAbs,
62
- outputFile: outputAbs,
63
- spriteSize,
64
- animationSpec
65
- });
66
- }
67
- catch (err) {
68
- return errorResult("character_import_from_concept", `Character import from concept failed: ${err instanceof Error ? err.message : String(err)}`);
69
- }
70
- };
71
- const character_generate_full = async ({ params: { prompt, config, paths } }, generator) => {
72
- try {
73
- const spriteSize = config?.spriteSize ?? 32;
74
- const animations = config?.animations ?? [
75
- { name: "Idle", frames: 4 },
76
- { name: "Walk", frames: 8 }
77
- ];
78
- const seed = config?.seed;
79
- const charName = (paths.name ??
80
- prompt
81
- .toLowerCase()
82
- .replace(/[^a-z0-9]+/g, "_")
83
- .replace(/^_+|_+$/g, "")
84
- .slice(0, 32)) ||
85
- "character";
86
- const workspaceBase = ensureSafePath(paths.workspaceDir, {
87
- mustExist: false
88
- });
89
- const exportBase = ensureSafePath(paths.exportDir, {
90
- mustExist: false
91
- });
92
- const workspaceDir = path.join(workspaceBase, charName);
93
- const exportDir = path.join(exportBase, charName);
94
- await fs.mkdir(workspaceDir, { recursive: true });
95
- await fs.mkdir(exportDir, { recursive: true });
96
- const conceptResult = await character_generate_concept({
97
- params: {
98
- prompt,
99
- workspaceDir,
100
- fileName: "concept.png",
101
- width: spriteSize * 8,
102
- height: spriteSize * 8,
103
- seed,
104
- stylePreset: "pixel_art"
105
- }
106
- }, generator);
107
- const conceptContent = conceptResult.content?.[0];
108
- const conceptJson = conceptContent
109
- ? JSON.parse(conceptContent.text)
110
- : null;
111
- if (!conceptJson || !conceptJson.success) {
112
- return errorResult("character_generate_full", {
113
- stage: "generate_concept",
114
- error: conceptJson?.error ?? "Concept generation failed"
115
- });
116
- }
117
- const conceptImage = conceptJson.result.imagePath;
118
- const animationSpec = animations
119
- .map((a) => `${a.name}:${a.frames}`)
120
- .join(",");
121
- const outputFile = path.join(workspaceDir, `${charName}.aseprite`);
122
- const importResult = await character_import_from_concept({
123
- params: {
124
- conceptImage,
125
- outputFile,
126
- spriteSize,
127
- animationSpec
128
- }
129
- }, {});
130
- const importContent = importResult.content?.[0];
131
- const importJson = importContent ? JSON.parse(importContent.text) : null;
132
- if (!importJson || !importJson.success) {
133
- return errorResult("character_generate_full", {
134
- stage: "import_sprite",
135
- error: importJson?.error ?? "Sprite import failed"
136
- });
137
- }
138
- const asepriteFile = importJson.result.outputFile;
139
- const pipelineToolHandlers = createPipelineToolHandlers();
140
- const buildResult = await pipelineToolHandlers.character_pipeline_build({
141
- inputFile: asepriteFile,
142
- exportDir
143
- }, {});
144
- const buildContent = buildResult.content?.[0];
145
- const buildJson = buildContent ? JSON.parse(buildContent.text) : null;
146
- if (!buildJson || !buildJson.success) {
147
- return errorResult("character_generate_full", {
148
- stage: "pipeline_build",
149
- error: buildJson?.error ?? "Character pipeline build failed"
150
- });
151
- }
152
- return successResult("character_generate_full", {
153
- name: charName,
154
- prompt,
155
- spriteSize,
156
- workspaceDir,
157
- exportDir,
158
- concept: conceptJson.result,
159
- asepriteFile,
160
- pipeline: buildJson.result
161
- });
162
- }
163
- catch (err) {
164
- return errorResult("character_generate_full", err instanceof Error ? err : new Error(String(err)));
165
- }
166
- };
167
- return {
168
- character_generate_concept: async (params, generator) => character_generate_concept(params, generator),
169
- character_import_from_concept,
170
- character_generate_full: async (params, generator) => character_generate_full(params, generator),
171
- };
172
- }
173
- export function createToolSchemas() {
174
- return {
175
- character_generate_concept: z.object({
176
- params: z.object({
177
- prompt: z.string(),
178
- workspaceDir: z.string(),
179
- fileName: z.string().optional(),
180
- width: z.number().optional(),
181
- height: z.number().optional(),
182
- seed: z.number().optional(),
183
- stylePreset: z.string().optional(),
184
- }),
185
- }),
186
- character_import_from_concept: z.object({
187
- params: z.object({
188
- conceptImage: z.string(),
189
- outputFile: z.string(),
190
- spriteSize: z.number().optional(),
191
- animationSpec: z.string().optional(),
192
- }),
193
- }),
194
- character_generate_full: z.object({
195
- params: z.object({
196
- prompt: z.string(),
197
- config: z.object({
198
- spriteSize: z.number().optional(),
199
- animations: z.array(z.object({ name: z.string(), frames: z.number() })).optional(),
200
- seed: z.number().optional(),
201
- }),
202
- paths: z.object({
203
- workspaceDir: z.string(),
204
- exportDir: z.string(),
205
- name: z.string().optional(),
206
- }),
207
- }),
208
- }),
209
- };
210
- }
@@ -1,16 +0,0 @@
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();
@@ -1,60 +0,0 @@
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
- }
@@ -1,66 +0,0 @@
1
- export function analyzeCharacterFromMetadata(inputFile, metaJson) {
2
- const framesArray = Object.values(metaJson.frames);
3
- const totalFrames = framesArray.length;
4
- const width = metaJson.meta.size?.w ?? 0;
5
- const height = metaJson.meta.size?.h ?? 0;
6
- const tags = metaJson.meta.frameTags ?? [];
7
- const tagAnalyses = [];
8
- const warnings = [];
9
- const recommendations = [];
10
- const recommendedTags = ["Idle", "Walk", "Attack"];
11
- for (const tag of tags) {
12
- const { name, from, to } = tag;
13
- const durationPattern = [];
14
- const issues = [];
15
- for (let i = from; i <= to; i++) {
16
- const frame = framesArray[i];
17
- if (frame) {
18
- durationPattern.push(frame.duration);
19
- }
20
- else {
21
- issues.push(`missing_frame_index_${i}`);
22
- }
23
- }
24
- if (durationPattern.length > 1) {
25
- const first = durationPattern[0];
26
- const inconsistent = durationPattern.some((duration) => duration !== first);
27
- if (inconsistent) {
28
- issues.push("duration_inconsistent");
29
- }
30
- }
31
- tagAnalyses.push({
32
- name,
33
- frames: to - from + 1,
34
- from,
35
- to,
36
- durationPattern,
37
- issues
38
- });
39
- }
40
- const existingTagNames = new Set(tags.map((tag) => tag.name));
41
- for (const tag of recommendedTags) {
42
- const exists = Array.from(existingTagNames).some((tagName) => tagName.toLowerCase() === tag.toLowerCase());
43
- if (!exists) {
44
- warnings.push(`Missing recommended animation tag: ${tag}`);
45
- }
46
- }
47
- if (tags.length === 0) {
48
- warnings.push("No frame tags found. Consider defining Idle/Walk/Attack tags.");
49
- recommendations.push("Define basic animation tags like Idle, Walk, and Attack.");
50
- }
51
- if (tagAnalyses.some((tagAnalysis) => tagAnalysis.issues.includes("duration_inconsistent"))) {
52
- warnings.push("Some tags have inconsistent frame durations.");
53
- recommendations.push("Normalize frame durations for smoother animations.");
54
- }
55
- return {
56
- file: inputFile,
57
- sprite: {
58
- width,
59
- height,
60
- frames: totalFrames
61
- },
62
- tags: tagAnalyses,
63
- warnings,
64
- recommendations
65
- };
66
- }
@@ -1,226 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import os from "node:os";
4
- import { fileURLToPath } from "node:url";
5
- import z from "zod";
6
- import { errorResult, successResult } from "../util.js";
7
- import { analyzeCharacterFromMetadata } from "./character.js";
8
- import { ensureSafePath } from "../aseprite/path.js";
9
- import { runAsepriteCommand } from "../aseprite/cli.js";
10
- import { runLuaScriptFile } from "../lua/cli.js";
11
- const __filename = fileURLToPath(import.meta.url);
12
- const __dirname = path.dirname(__filename);
13
- const toolSchemas = createToolSchemas();
14
- export function createToolHandlers() {
15
- const character_pipeline_analyze = async ({ inputFile }) => {
16
- try {
17
- const inputAbs = ensureSafePath(inputFile, { mustExist: true });
18
- const tempJsonPath = path.join(os.tmpdir(), `${inputFile}-analyze-${Date.now()}.json`);
19
- const args = [
20
- "--batch",
21
- `"${inputAbs}"`,
22
- "--data",
23
- `"${tempJsonPath}"`,
24
- "--list-tags",
25
- "--list-layers"
26
- ];
27
- const result = await runAsepriteCommand(args);
28
- const metaJson = await fs.readFile(tempJsonPath, "utf8");
29
- const parsedMeta = JSON.parse(metaJson);
30
- const analysis = analyzeCharacterFromMetadata(inputAbs, parsedMeta);
31
- return successResult("character_pipeline_analyze", {
32
- command: result.command,
33
- inputFile: inputAbs,
34
- analysis,
35
- stdout: result.stdout.trim(),
36
- stderr: result.stderr.trim(),
37
- });
38
- }
39
- catch (err) {
40
- return errorResult("character_pipeline_analyze", `Character analysis failed: ${err instanceof Error ? err.message : String(err)}`);
41
- }
42
- };
43
- const character_pipeline_normalize = async ({ inputFile, saveOutput, targetMs, autoCrop }) => {
44
- try {
45
- const inputAbs = ensureSafePath(inputFile, { mustExist: true });
46
- const baseDir = path.dirname(inputAbs);
47
- const baseName = path.basename(inputAbs, path.extname(inputAbs));
48
- const outputPath = saveOutput ??
49
- path.join(baseDir, `${baseName}_normalized.aseprite`);
50
- const outputAbs = ensureSafePath(outputPath, { mustExist: false });
51
- const targetDuration = targetMs ?? 100;
52
- const autoCropEnabled = autoCrop ?? true;
53
- // Lua templates are in the source directory, reference from project root
54
- const luaScriptPath = path.join(process.cwd(), "src", "lua", "templates", "character_normalize.lua");
55
- const result = await runLuaScriptFile(luaScriptPath, {
56
- inputFile: inputAbs,
57
- saveOutput: outputAbs,
58
- targetMs: targetDuration,
59
- autoCrop: autoCropEnabled
60
- });
61
- if (result.timedOut) {
62
- return errorResult("character_pipeline_normalize", "Lua script timed out while normalizing character");
63
- }
64
- return successResult("character_pipeline_normalize", {
65
- command: result.command,
66
- inputFile: inputAbs,
67
- outputFile: outputAbs,
68
- targetMs: targetDuration,
69
- autoCrop: autoCropEnabled,
70
- stdout: result.stdout.trim(),
71
- stderr: result.stderr.trim(),
72
- });
73
- }
74
- catch (err) {
75
- return errorResult("character_pipeline_normalize", `Character normalization failed: ${err instanceof Error ? err.message : String(err)}`);
76
- }
77
- };
78
- const character_pipeline_export = async ({ inputFile, exportDir, sheetType = "packed", format = "json-hash" }) => {
79
- try {
80
- const inputAbs = ensureSafePath(inputFile, { mustExist: true });
81
- const exportDirAbs = ensureSafePath(exportDir, { mustExist: false });
82
- await fs.mkdir(exportDirAbs, { recursive: true });
83
- const tempJsonPath = path.join(os.tmpdir(), `${inputFile}-export-${Date.now()}.json`);
84
- const metaArgs = [
85
- "--batch",
86
- `"${inputAbs}"`,
87
- "--data",
88
- `"${tempJsonPath}"`,
89
- "--list-tags"
90
- ];
91
- await runAsepriteCommand(metaArgs);
92
- const metaJson = await fs.readFile(tempJsonPath, "utf8");
93
- const parsedMeta = JSON.parse(metaJson);
94
- const tags = parsedMeta.meta.frameTags ?? [];
95
- if (tags.length === 0) {
96
- return errorResult("character_pipeline_export", "No tags found in sprite. Define animation tags before export.");
97
- }
98
- const baseName = path.basename(inputAbs, path.extname(inputAbs));
99
- const generated = [];
100
- for (const tag of tags) {
101
- const tagName = tag.name;
102
- const safeTag = tagName.toLowerCase();
103
- const pngPath = path.join(exportDirAbs, `${baseName}_${safeTag}.png`);
104
- const jsonPath = path.join(exportDirAbs, `${baseName}_${safeTag}.json`);
105
- const args = [
106
- "--batch",
107
- `"${inputAbs}"`,
108
- "--tag",
109
- `"${tagName}"`,
110
- "--sheet",
111
- `"${pngPath}"`,
112
- "--data",
113
- `"${jsonPath}"`,
114
- "--sheet-type",
115
- sheetType,
116
- "--format",
117
- format
118
- ];
119
- await runAsepriteCommand(args);
120
- generated.push({
121
- tag: tagName,
122
- png: pngPath,
123
- json: jsonPath,
124
- frames: tag.to - tag.from + 1
125
- });
126
- }
127
- return successResult("character_pipeline_export", {
128
- inputFile: inputAbs,
129
- exportDir: exportDirAbs,
130
- sheetType,
131
- format,
132
- generated,
133
- });
134
- }
135
- catch (err) {
136
- return errorResult("character_pipeline_export", `Character export failed: ${err instanceof Error ? err.message : String(err)}`);
137
- }
138
- };
139
- const character_pipeline_build = async ({ inputFile, tempOutput, exportDir, normalizeOption = { targetMs: 100, autoCrop: true }, exportOption = { sheetType: "packed", format: "json-hash" } }) => {
140
- try {
141
- const inputAbs = ensureSafePath(inputFile, { mustExist: true });
142
- const baseDir = path.dirname(inputAbs);
143
- const baseName = path.basename(inputAbs, path.extname(inputAbs));
144
- const normalizedOutput = tempOutput ??
145
- path.join(baseDir, `${baseName}_normalized.aseprite`);
146
- const exportDirAbs = ensureSafePath(exportDir, { mustExist: false });
147
- const analyzeResponse = await character_pipeline_analyze({ inputFile: inputAbs }, {});
148
- const analyzeContent = analyzeResponse.content[0];
149
- const analyzeParsed = JSON.parse(analyzeContent.text);
150
- if (!analyzeParsed.success) {
151
- return errorResult("character_pipeline_build", analyzeParsed.error);
152
- }
153
- const normalizeResponse = await character_pipeline_normalize({
154
- inputFile: inputAbs,
155
- saveOutput: normalizedOutput,
156
- targetMs: normalizeOption.targetMs,
157
- autoCrop: normalizeOption.autoCrop,
158
- }, {});
159
- const normalizeContent = normalizeResponse.content[0];
160
- const normalizeParsed = JSON.parse(normalizeContent.text);
161
- if (!normalizeParsed.success) {
162
- return errorResult("character_pipeline_build", normalizeParsed.error);
163
- }
164
- const exportResponse = await character_pipeline_export({
165
- inputFile: normalizedOutput,
166
- exportDir: exportDirAbs,
167
- sheetType: exportOption.sheetType,
168
- format: exportOption.format,
169
- }, {});
170
- const exportContent = exportResponse.content[0];
171
- const exportParsed = JSON.parse(exportContent.text);
172
- if (!exportParsed.success) {
173
- return errorResult("character_pipeline_build", exportParsed.error);
174
- }
175
- return successResult("character_pipeline_build", {
176
- inputFile: inputAbs,
177
- normalizedFile: normalizedOutput,
178
- exportDir: exportDirAbs,
179
- analyze: analyzeParsed.result,
180
- normalize: normalizeParsed.result,
181
- export: exportParsed.result,
182
- });
183
- }
184
- catch (err) {
185
- return errorResult("character_pipeline_build", `Character pipeline failed: ${err instanceof Error ? err.message : String(err)}`);
186
- }
187
- };
188
- return {
189
- character_pipeline_analyze,
190
- character_pipeline_normalize,
191
- character_pipeline_export,
192
- character_pipeline_build,
193
- };
194
- }
195
- export function createToolSchemas() {
196
- return {
197
- character_pipeline_analyze: z.object({
198
- inputFile: z.string(),
199
- }),
200
- character_pipeline_normalize: z.object({
201
- inputFile: z.string(),
202
- saveOutput: z.string().optional(),
203
- targetMs: z.number().optional(),
204
- autoCrop: z.boolean().optional(),
205
- }),
206
- character_pipeline_export: z.object({
207
- inputFile: z.string(),
208
- exportDir: z.string(),
209
- sheetType: z.enum(["packed", "rows"]).optional(),
210
- format: z.enum(["json-hash", "json-array"]).optional(),
211
- }),
212
- character_pipeline_build: z.object({
213
- inputFile: z.string(),
214
- tempOutput: z.string().optional(),
215
- exportDir: z.string(),
216
- normalizeOption: z.object({
217
- targetMs: z.number().optional(),
218
- autoCrop: z.boolean().optional(),
219
- }).optional(),
220
- exportOption: z.object({
221
- sheetType: z.enum(["packed", "rows"]).optional(),
222
- format: z.enum(["json-hash", "json-array"]).optional(),
223
- }).optional(),
224
- }),
225
- };
226
- }
package/build/server.js DELETED
@@ -1,177 +0,0 @@
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 DELETED
@@ -1,145 +0,0 @@
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
- }