@trishchuk/coolors-mcp 1.0.1 → 1.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/.github/workflows/ci.yml +23 -20
- package/.github/workflows/deploy-docs.yml +6 -3
- package/.github/workflows/release.yml +11 -9
- package/README.md +123 -14
- package/dist/bin/server.js +997 -256
- package/dist/bin/server.js.map +1 -1
- package/dist/{chunk-P3ARRKLS.js → chunk-HOMDMKUY.js} +3 -1
- package/dist/{chunk-P3ARRKLS.js.map → chunk-HOMDMKUY.js.map} +1 -1
- package/dist/{chunk-IQ7NN26V.js → chunk-LHW2ZTOU.js} +14 -2
- package/dist/chunk-LHW2ZTOU.js.map +1 -0
- package/dist/color/index.js +1 -1
- package/dist/coolors-mcp.d.ts +4 -4
- package/dist/coolors-mcp.js +1 -1
- package/eslint.config.ts +13 -0
- package/jsr.json +1 -1
- package/package.json +16 -12
- package/src/bin/server.ts +13 -1
- package/src/color/__tests__/extract-colors.test.ts +20 -30
- package/src/color/apca.ts +105 -0
- package/src/color/color-blindness.ts +109 -0
- package/src/coolors-mcp.ts +1 -1
- package/src/session.ts +10 -2
- package/src/theme/matcher.ts +1 -1
- package/src/theme/refactor.ts +1 -1
- package/src/theme/types.ts +3 -0
- package/src/tools/__tests__/cohesion.test.ts +97 -0
- package/src/tools/__tests__/color-blindness.test.ts +45 -0
- package/src/tools/__tests__/color-conversion.test.ts +38 -0
- package/src/tools/__tests__/contrast-checker.test.ts +56 -0
- package/src/tools/__tests__/palette-export.test.ts +54 -0
- package/src/tools/adjust-color.tool.ts +80 -0
- package/src/tools/cohesion.tools.ts +380 -0
- package/src/tools/color-blindness.tool.ts +168 -0
- package/src/tools/color-conversion.tool.ts +1 -1
- package/src/tools/contrast-checker.tool.ts +53 -14
- package/src/tools/dislike-analyzer.tool.ts +41 -54
- package/src/tools/image-extraction.tools.ts +62 -115
- package/src/tools/index.ts +15 -2
- package/src/tools/palette-export.tool.ts +174 -0
- package/src/tools/palette-with-locks.tool.ts +8 -6
- package/src/types.ts +2 -3
- package/tsconfig.json +12 -2
- package/vitest.config.js +1 -3
- package/.claude/settings.local.json +0 -35
- package/.env +0 -2
- package/.mcp.json +0 -12
- package/CLAUDE.md +0 -201
- package/DOCUMENTATION.md +0 -274
- package/GEMINI.md +0 -54
- package/TOOLS_UK.md +0 -233
- package/demo/content_based_color.png +0 -0
- package/demo/music-player.html +0 -621
- package/demo/podcast-player.html +0 -903
- package/dist/chunk-IQ7NN26V.js.map +0 -1
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js +0 -111
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js.map +0 -7
- package/docs/.vitepress/cache/deps/_metadata.json +0 -127
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js +0 -12
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +0 -7
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +0 -13614
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js.map +0 -7
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +0 -10698
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js.map +0 -7
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +0 -5609
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +0 -7
- package/docs/.vitepress/cache/deps/cytoscape.js +0 -36234
- package/docs/.vitepress/cache/deps/cytoscape.js.map +0 -7
- package/docs/.vitepress/cache/deps/dayjs.js +0 -507
- package/docs/.vitepress/cache/deps/dayjs.js.map +0 -7
- package/docs/.vitepress/cache/deps/debug.js +0 -512
- package/docs/.vitepress/cache/deps/debug.js.map +0 -7
- package/docs/.vitepress/cache/deps/package.json +0 -3
- package/docs/.vitepress/cache/deps/prismjs.js +0 -1638
- package/docs/.vitepress/cache/deps/prismjs.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +0 -235
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +0 -173
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js +0 -27
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js +0 -72
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +0 -56
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +0 -107
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -5074
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -584
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +0 -1483
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +0 -1779
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js +0 -2023
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js.map +0 -7
- package/docs/.vitepress/cache/deps/vue.js +0 -344
- package/docs/.vitepress/cache/deps/vue.js.map +0 -7
- package/examples/theme-matching.md +0 -113
- package/mcp-config.json +0 -8
- package/note.md +0 -34
- package/research_results.md +0 -53
- package/src/tools/colors.ts +0 -31
- package/src/tools/registry.ts +0 -142
- package/src/tools/simple-tools.ts +0 -37
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Contrast Checker Tool
|
|
3
|
-
* Check
|
|
3
|
+
* Check contrast between two colors using WCAG 2.x luminance ratio,
|
|
4
|
+
* APCA (WCAG 3 draft), or both.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { z } from "zod";
|
|
7
8
|
|
|
9
|
+
import { apcaContrast, apcaLevel } from "../color/apca.js";
|
|
8
10
|
import { getContrastRatio, parseColor } from "../color/index.js";
|
|
9
11
|
|
|
10
12
|
export const contrastCheckerTool = {
|
|
11
|
-
description:
|
|
12
|
-
|
|
13
|
+
description:
|
|
14
|
+
"Check contrast between two colors. Supports WCAG 2.x luminance ratio (default), APCA Lc (WCAG 3 draft), or both algorithms side-by-side.",
|
|
15
|
+
execute: async (args: {
|
|
16
|
+
algorithm?: "apca" | "both" | "wcag";
|
|
17
|
+
background: string;
|
|
18
|
+
foreground: string;
|
|
19
|
+
}) => {
|
|
13
20
|
const fg = parseColor(args.foreground);
|
|
14
21
|
const bg = parseColor(args.background);
|
|
15
22
|
|
|
@@ -17,20 +24,52 @@ export const contrastCheckerTool = {
|
|
|
17
24
|
return `Invalid color format: ${!fg ? args.foreground : args.background}`;
|
|
18
25
|
}
|
|
19
26
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
const algorithm = args.algorithm ?? "wcag";
|
|
28
|
+
|
|
29
|
+
const wcagBlock = () => {
|
|
30
|
+
const ratio = getContrastRatio(fg, bg);
|
|
31
|
+
const aaLarge = ratio >= 3;
|
|
32
|
+
const aa = ratio >= 4.5;
|
|
33
|
+
const aaaLarge = ratio >= 4.5;
|
|
34
|
+
const aaa = ratio >= 7;
|
|
35
|
+
return `## WCAG 2.x luminance ratio
|
|
36
|
+
Contrast Ratio: ${ratio.toFixed(2)}:1
|
|
37
|
+
- AA (normal text): ${aa ? "✓ Pass" : "✗ Fail"} (need 4.5:1)
|
|
38
|
+
- AA (large text): ${aaLarge ? "✓ Pass" : "✗ Fail"} (need 3:1)
|
|
39
|
+
- AAA (normal text): ${aaa ? "✓ Pass" : "✗ Fail"} (need 7:1)
|
|
40
|
+
- AAA (large text): ${aaaLarge ? "✓ Pass" : "✗ Fail"} (need 4.5:1)`;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const apcaBlock = () => {
|
|
44
|
+
const lc = apcaContrast(fg, bg);
|
|
45
|
+
const level = apcaLevel(lc);
|
|
46
|
+
const polarity =
|
|
47
|
+
lc > 0
|
|
48
|
+
? "dark text on light bg"
|
|
49
|
+
: lc < 0
|
|
50
|
+
? "light text on dark bg"
|
|
51
|
+
: "no contrast";
|
|
52
|
+
return `## APCA (WCAG 3 draft)
|
|
53
|
+
Lc: ${lc.toFixed(1)} (${polarity})
|
|
54
|
+
- Body text (|Lc| ≥ 75): ${level.body ? "✓ Pass" : "✗ Fail"}
|
|
55
|
+
- Content text (|Lc| ≥ 60): ${level.content ? "✓ Pass" : "✗ Fail"}
|
|
56
|
+
- Large text (|Lc| ≥ 45): ${level.large ? "✓ Pass" : "✗ Fail"}
|
|
57
|
+
- Spot / non-content (|Lc| ≥ 30): ${level.spot ? "✓ Pass" : "✗ Fail"}`;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (algorithm === "wcag") return wcagBlock();
|
|
61
|
+
if (algorithm === "apca") return apcaBlock();
|
|
62
|
+
return `${wcagBlock()}\n\n${apcaBlock()}`;
|
|
31
63
|
},
|
|
32
64
|
name: "check_contrast",
|
|
33
65
|
parameters: z.object({
|
|
66
|
+
algorithm: z
|
|
67
|
+
.enum(["wcag", "apca", "both"])
|
|
68
|
+
.optional()
|
|
69
|
+
.default("wcag")
|
|
70
|
+
.describe(
|
|
71
|
+
"Contrast algorithm: 'wcag' (WCAG 2.x ratio), 'apca' (Lc, WCAG 3 draft), or 'both'",
|
|
72
|
+
),
|
|
34
73
|
background: z.string().describe("Background color"),
|
|
35
74
|
foreground: z.string().describe("Foreground/text color"),
|
|
36
75
|
}),
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
* Identifies and fixes universally disliked colors based on color psychology research
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
type McpTool = Tool<any>;
|
|
6
|
+
import { z } from "zod";
|
|
9
7
|
|
|
10
8
|
import { DislikeAnalyzer } from "../color/dislike/dislike-analyzer.js";
|
|
11
9
|
import { Hct } from "../color/hct/hct-class.js";
|
|
@@ -14,14 +12,11 @@ import { parseColor, rgbToArgb } from "../color/index.js";
|
|
|
14
12
|
/**
|
|
15
13
|
* Analyze if a color is universally disliked
|
|
16
14
|
*/
|
|
17
|
-
export const analyzeColorLikabilityTool
|
|
15
|
+
export const analyzeColorLikabilityTool = {
|
|
18
16
|
description:
|
|
19
17
|
"Check if a color is universally disliked (dark yellow-green associated with biological waste) and get a fixed version if needed",
|
|
20
|
-
execute: async (args:
|
|
21
|
-
const { autoFix = true, color } = args
|
|
22
|
-
autoFix?: boolean;
|
|
23
|
-
color: string;
|
|
24
|
-
};
|
|
18
|
+
execute: async (args: { autoFix?: boolean; color: string }) => {
|
|
19
|
+
const { autoFix = true, color } = args;
|
|
25
20
|
|
|
26
21
|
try {
|
|
27
22
|
// Parse the input color
|
|
@@ -95,40 +90,37 @@ export const analyzeColorLikabilityTool: McpTool = {
|
|
|
95
90
|
return `Error analyzing color: ${error instanceof Error ? error.message : String(error)}`;
|
|
96
91
|
}
|
|
97
92
|
},
|
|
98
|
-
inputSchema: {
|
|
99
|
-
properties: {
|
|
100
|
-
autoFix: {
|
|
101
|
-
description:
|
|
102
|
-
"Automatically return fixed version if disliked (default: true)",
|
|
103
|
-
type: "boolean",
|
|
104
|
-
},
|
|
105
|
-
color: {
|
|
106
|
-
description: "Color to analyze (hex, rgb, hsl, etc.)",
|
|
107
|
-
type: "string",
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
required: ["color"],
|
|
111
|
-
type: "object",
|
|
112
|
-
},
|
|
113
93
|
name: "analyze_color_likability",
|
|
94
|
+
parameters: z.object({
|
|
95
|
+
autoFix: z
|
|
96
|
+
.boolean()
|
|
97
|
+
.optional()
|
|
98
|
+
.default(true)
|
|
99
|
+
.describe("Automatically return fixed version if disliked"),
|
|
100
|
+
color: z.string().describe("Color to analyze (hex, rgb, hsl, etc.)"),
|
|
101
|
+
}),
|
|
114
102
|
};
|
|
115
103
|
|
|
116
104
|
/**
|
|
117
105
|
* Fix multiple disliked colors in a batch
|
|
118
106
|
*/
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
includeAnalysis?: boolean;
|
|
107
|
+
type BatchHct =
|
|
108
|
+
| { c: number; h: number; t: number }
|
|
109
|
+
| {
|
|
110
|
+
fixed: { c: number; h: number; t: number };
|
|
111
|
+
original: { c: number; h: number; t: number };
|
|
125
112
|
};
|
|
126
113
|
|
|
114
|
+
export const fixDislikedColorsBatchTool = {
|
|
115
|
+
description: "Analyze and fix multiple colors, returning only liked versions",
|
|
116
|
+
execute: async (args: { colors: string[]; includeAnalysis?: boolean }) => {
|
|
117
|
+
const { colors, includeAnalysis = false } = args;
|
|
118
|
+
|
|
127
119
|
try {
|
|
128
120
|
const results: Array<{
|
|
129
121
|
error?: string;
|
|
130
122
|
fixed?: string;
|
|
131
|
-
hct?:
|
|
123
|
+
hct?: BatchHct;
|
|
132
124
|
original: string;
|
|
133
125
|
wasDisliked?: boolean;
|
|
134
126
|
}> = [];
|
|
@@ -202,14 +194,16 @@ export const fixDislikedColorsBatchTool: McpTool = {
|
|
|
202
194
|
output += `- **${result.original}**: ❌ ${result.error}\n`;
|
|
203
195
|
} else if (result.wasDisliked) {
|
|
204
196
|
output += `- **${result.original}** → **${result.fixed}** (fixed)\n`;
|
|
205
|
-
if (includeAnalysis && result.hct) {
|
|
206
|
-
|
|
207
|
-
output += ` -
|
|
197
|
+
if (includeAnalysis && result.hct && "original" in result.hct) {
|
|
198
|
+
const { fixed, original } = result.hct;
|
|
199
|
+
output += ` - Original HCT: (${original.h.toFixed(0)}°, ${original.c.toFixed(0)}, ${original.t.toFixed(0)})\n`;
|
|
200
|
+
output += ` - Fixed HCT: (${fixed.h.toFixed(0)}°, ${fixed.c.toFixed(0)}, ${fixed.t.toFixed(0)})\n`;
|
|
208
201
|
}
|
|
209
202
|
} else {
|
|
210
203
|
output += `- **${result.original}** ✓ (already liked)\n`;
|
|
211
|
-
if (includeAnalysis && result.hct) {
|
|
212
|
-
|
|
204
|
+
if (includeAnalysis && result.hct && "h" in result.hct) {
|
|
205
|
+
const { c, h, t } = result.hct;
|
|
206
|
+
output += ` - HCT: (${h.toFixed(0)}°, ${c.toFixed(0)}, ${t.toFixed(0)})\n`;
|
|
213
207
|
}
|
|
214
208
|
}
|
|
215
209
|
}
|
|
@@ -225,23 +219,16 @@ export const fixDislikedColorsBatchTool: McpTool = {
|
|
|
225
219
|
return `Error processing colors: ${error instanceof Error ? error.message : String(error)}`;
|
|
226
220
|
}
|
|
227
221
|
},
|
|
228
|
-
inputSchema: {
|
|
229
|
-
properties: {
|
|
230
|
-
colors: {
|
|
231
|
-
description: "Array of colors to analyze (hex, rgb, hsl, etc.)",
|
|
232
|
-
items: {
|
|
233
|
-
type: "string",
|
|
234
|
-
},
|
|
235
|
-
type: "array",
|
|
236
|
-
},
|
|
237
|
-
includeAnalysis: {
|
|
238
|
-
description:
|
|
239
|
-
"Include detailed analysis for each color (default: false)",
|
|
240
|
-
type: "boolean",
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
required: ["colors"],
|
|
244
|
-
type: "object",
|
|
245
|
-
},
|
|
246
222
|
name: "fix_disliked_colors_batch",
|
|
223
|
+
parameters: z.object({
|
|
224
|
+
colors: z
|
|
225
|
+
.array(z.string())
|
|
226
|
+
.min(1)
|
|
227
|
+
.describe("Array of colors to analyze (hex, rgb, hsl, etc.)"),
|
|
228
|
+
includeAnalysis: z
|
|
229
|
+
.boolean()
|
|
230
|
+
.optional()
|
|
231
|
+
.default(false)
|
|
232
|
+
.describe("Include detailed analysis for each color"),
|
|
233
|
+
}),
|
|
247
234
|
};
|
|
@@ -2,15 +2,7 @@
|
|
|
2
2
|
* MCP tools for image color extraction
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
type ImageData = {
|
|
8
|
-
data: number[] | Uint8ClampedArray;
|
|
9
|
-
height: number;
|
|
10
|
-
width: number;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
type McpTool = Tool<unknown>;
|
|
5
|
+
import { z } from "zod";
|
|
14
6
|
|
|
15
7
|
import {
|
|
16
8
|
extractColors,
|
|
@@ -19,31 +11,37 @@ import {
|
|
|
19
11
|
} from "../color/extract-colors.js";
|
|
20
12
|
import { generateMaterialTheme } from "../color/material-theme.js";
|
|
21
13
|
|
|
14
|
+
const imageDataSchema = z
|
|
15
|
+
.object({
|
|
16
|
+
data: z.array(z.number()).describe("Flat array of RGBA values (0-255)"),
|
|
17
|
+
height: z.number().int().positive().describe("Image height in pixels"),
|
|
18
|
+
width: z.number().int().positive().describe("Image width in pixels"),
|
|
19
|
+
})
|
|
20
|
+
.describe("Image data with RGBA values");
|
|
21
|
+
|
|
22
|
+
type ImageDataInput = z.infer<typeof imageDataSchema>;
|
|
23
|
+
|
|
22
24
|
/**
|
|
23
25
|
* Extract dominant colors from image data
|
|
24
26
|
*/
|
|
25
|
-
export const extractImageColorsTool
|
|
27
|
+
export const extractImageColorsTool = {
|
|
26
28
|
description:
|
|
27
29
|
"Extract dominant colors from an image. Input should be image data as an array of RGBA values.",
|
|
28
|
-
execute: async (args:
|
|
30
|
+
execute: async (args: {
|
|
31
|
+
format?: "css" | "json" | "palette";
|
|
32
|
+
imageData: ImageDataInput;
|
|
33
|
+
maxColors?: number;
|
|
34
|
+
quality?: "high" | "low" | "medium";
|
|
35
|
+
}) => {
|
|
29
36
|
const {
|
|
30
37
|
format = "json",
|
|
31
38
|
imageData,
|
|
32
39
|
maxColors = 5,
|
|
33
40
|
quality = "medium",
|
|
34
|
-
} = args
|
|
35
|
-
format?: string;
|
|
36
|
-
imageData: ImageData;
|
|
37
|
-
maxColors?: number;
|
|
38
|
-
quality?: "high" | "low" | "medium";
|
|
39
|
-
};
|
|
41
|
+
} = args;
|
|
40
42
|
|
|
41
43
|
try {
|
|
42
|
-
|
|
43
|
-
const data =
|
|
44
|
-
imageData.data instanceof Uint8ClampedArray
|
|
45
|
-
? imageData.data
|
|
46
|
-
: new Uint8ClampedArray(imageData.data);
|
|
44
|
+
const data = new Uint8ClampedArray(imageData.data);
|
|
47
45
|
|
|
48
46
|
const processedImageData = {
|
|
49
47
|
data,
|
|
@@ -75,74 +73,44 @@ export const extractImageColorsTool: McpTool = {
|
|
|
75
73
|
return `Error extracting colors: ${error instanceof Error ? error.message : String(error)}`;
|
|
76
74
|
}
|
|
77
75
|
},
|
|
78
|
-
inputSchema: {
|
|
79
|
-
properties: {
|
|
80
|
-
format: {
|
|
81
|
-
description: "Output format: json, css, or palette (default: json)",
|
|
82
|
-
enum: ["json", "css", "palette"],
|
|
83
|
-
type: "string",
|
|
84
|
-
},
|
|
85
|
-
imageData: {
|
|
86
|
-
description: "Image data with RGBA values",
|
|
87
|
-
properties: {
|
|
88
|
-
data: {
|
|
89
|
-
description: "Flat array of RGBA values (0-255)",
|
|
90
|
-
items: { type: "number" },
|
|
91
|
-
type: "array",
|
|
92
|
-
},
|
|
93
|
-
height: {
|
|
94
|
-
description: "Image height in pixels",
|
|
95
|
-
type: "number",
|
|
96
|
-
},
|
|
97
|
-
width: {
|
|
98
|
-
description: "Image width in pixels",
|
|
99
|
-
type: "number",
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
required: ["data", "width", "height"],
|
|
103
|
-
type: "object",
|
|
104
|
-
},
|
|
105
|
-
maxColors: {
|
|
106
|
-
description: "Maximum number of colors to extract (default: 5)",
|
|
107
|
-
maximum: 20,
|
|
108
|
-
minimum: 1,
|
|
109
|
-
type: "number",
|
|
110
|
-
},
|
|
111
|
-
quality: {
|
|
112
|
-
description:
|
|
113
|
-
"Extraction quality: low, medium, or high (default: medium)",
|
|
114
|
-
enum: ["low", "medium", "high"],
|
|
115
|
-
type: "string",
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
required: ["imageData"],
|
|
119
|
-
type: "object",
|
|
120
|
-
},
|
|
121
76
|
name: "extract_image_colors",
|
|
77
|
+
parameters: z.object({
|
|
78
|
+
format: z
|
|
79
|
+
.enum(["json", "css", "palette"])
|
|
80
|
+
.optional()
|
|
81
|
+
.default("json")
|
|
82
|
+
.describe("Output format"),
|
|
83
|
+
imageData: imageDataSchema,
|
|
84
|
+
maxColors: z
|
|
85
|
+
.number()
|
|
86
|
+
.int()
|
|
87
|
+
.min(1)
|
|
88
|
+
.max(20)
|
|
89
|
+
.optional()
|
|
90
|
+
.default(5)
|
|
91
|
+
.describe("Maximum number of colors to extract"),
|
|
92
|
+
quality: z
|
|
93
|
+
.enum(["low", "medium", "high"])
|
|
94
|
+
.optional()
|
|
95
|
+
.default("medium")
|
|
96
|
+
.describe("Extraction quality"),
|
|
97
|
+
}),
|
|
122
98
|
};
|
|
123
99
|
|
|
124
100
|
/**
|
|
125
101
|
* Generate a Material Design theme from an image
|
|
126
102
|
*/
|
|
127
|
-
export const generateThemeFromImageTool
|
|
103
|
+
export const generateThemeFromImageTool = {
|
|
128
104
|
description: "Generate a complete Material Design 3 theme from an image",
|
|
129
|
-
execute: async (args:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
} = args
|
|
135
|
-
imageData: ImageData;
|
|
136
|
-
includeCustomColors?: boolean;
|
|
137
|
-
isDark?: boolean;
|
|
138
|
-
};
|
|
105
|
+
execute: async (args: {
|
|
106
|
+
imageData: ImageDataInput;
|
|
107
|
+
includeCustomColors?: boolean;
|
|
108
|
+
isDark?: boolean;
|
|
109
|
+
}) => {
|
|
110
|
+
const { imageData, includeCustomColors = true, isDark = false } = args;
|
|
139
111
|
|
|
140
112
|
try {
|
|
141
|
-
|
|
142
|
-
const data =
|
|
143
|
-
imageData.data instanceof Uint8ClampedArray
|
|
144
|
-
? imageData.data
|
|
145
|
-
: new Uint8ClampedArray(imageData.data);
|
|
113
|
+
const data = new Uint8ClampedArray(imageData.data);
|
|
146
114
|
|
|
147
115
|
const processedImageData = {
|
|
148
116
|
data,
|
|
@@ -212,41 +180,20 @@ export const generateThemeFromImageTool: McpTool = {
|
|
|
212
180
|
return `Error generating theme: ${error instanceof Error ? error.message : String(error)}`;
|
|
213
181
|
}
|
|
214
182
|
},
|
|
215
|
-
inputSchema: {
|
|
216
|
-
properties: {
|
|
217
|
-
imageData: {
|
|
218
|
-
description: "Image data with RGBA values",
|
|
219
|
-
properties: {
|
|
220
|
-
data: {
|
|
221
|
-
description: "Flat array of RGBA values (0-255)",
|
|
222
|
-
items: { type: "number" },
|
|
223
|
-
type: "array",
|
|
224
|
-
},
|
|
225
|
-
height: {
|
|
226
|
-
description: "Image height in pixels",
|
|
227
|
-
type: "number",
|
|
228
|
-
},
|
|
229
|
-
width: {
|
|
230
|
-
description: "Image width in pixels",
|
|
231
|
-
type: "number",
|
|
232
|
-
},
|
|
233
|
-
},
|
|
234
|
-
required: ["data", "width", "height"],
|
|
235
|
-
type: "object",
|
|
236
|
-
},
|
|
237
|
-
includeCustomColors: {
|
|
238
|
-
description: "Include custom colors from image (default: true)",
|
|
239
|
-
type: "boolean",
|
|
240
|
-
},
|
|
241
|
-
isDark: {
|
|
242
|
-
description: "Generate dark theme (default: false for light theme)",
|
|
243
|
-
type: "boolean",
|
|
244
|
-
},
|
|
245
|
-
},
|
|
246
|
-
required: ["imageData"],
|
|
247
|
-
type: "object",
|
|
248
|
-
},
|
|
249
183
|
name: "generate_theme_from_image",
|
|
184
|
+
parameters: z.object({
|
|
185
|
+
imageData: imageDataSchema,
|
|
186
|
+
includeCustomColors: z
|
|
187
|
+
.boolean()
|
|
188
|
+
.optional()
|
|
189
|
+
.default(true)
|
|
190
|
+
.describe("Include custom colors from image"),
|
|
191
|
+
isDark: z
|
|
192
|
+
.boolean()
|
|
193
|
+
.optional()
|
|
194
|
+
.default(false)
|
|
195
|
+
.describe("Generate dark theme (false for light theme)"),
|
|
196
|
+
}),
|
|
250
197
|
};
|
|
251
198
|
|
|
252
199
|
// Helper functions for formatting
|
package/src/tools/index.ts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
// Tool Registry Index - Export all tools
|
|
2
2
|
|
|
3
|
+
export { adjustColorTool } from "./adjust-color.tool.js";
|
|
4
|
+
// Visual cohesion: tonal scales, state colors, palette consistency
|
|
5
|
+
export {
|
|
6
|
+
analyzePaletteConsistencyTool,
|
|
7
|
+
generateSemanticPaletteTool,
|
|
8
|
+
generateStateColorsTool,
|
|
9
|
+
generateTonalScaleTool,
|
|
10
|
+
} from "./cohesion.tools.js";
|
|
11
|
+
// Color-blindness simulation & accessibility audit
|
|
12
|
+
export {
|
|
13
|
+
checkPaletteAccessibilityTool,
|
|
14
|
+
simulateColorBlindnessTool,
|
|
15
|
+
} from "./color-blindness.tool.js";
|
|
3
16
|
// Color tools
|
|
4
17
|
export { colorConversionTool } from "./color-conversion.tool.js";
|
|
5
18
|
export { colorDistanceTool } from "./color-distance.tool.js";
|
|
6
|
-
// Legacy tools (if needed)
|
|
7
|
-
export { convertColor } from "./colors.js";
|
|
8
19
|
export { contrastCheckerTool } from "./contrast-checker.tool.js";
|
|
9
20
|
|
|
10
21
|
// Dislike analyzer tools
|
|
@@ -27,6 +38,8 @@ export {
|
|
|
27
38
|
generateTonalPaletteTool,
|
|
28
39
|
harmonizeColorsTool,
|
|
29
40
|
} from "./material-theme.tools.js";
|
|
41
|
+
|
|
42
|
+
export { exportPaletteTool } from "./palette-export.tool.js";
|
|
30
43
|
export { paletteGeneratorTool } from "./palette-generator.tool.js";
|
|
31
44
|
export { paletteWithLocksTool } from "./palette-with-locks.tool.js";
|
|
32
45
|
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Palette Export Tool
|
|
3
|
+
* Convert a palette of colors into commonly used design-system formats:
|
|
4
|
+
* CSS custom properties, SCSS variables, Tailwind config, or W3C design tokens.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
import { parseColor, rgbToHex, rgbToHsl } from "../color/index.js";
|
|
10
|
+
|
|
11
|
+
const FORMATS = ["css", "scss", "tailwind", "tokens", "json"] as const;
|
|
12
|
+
|
|
13
|
+
type ExportFormat = (typeof FORMATS)[number];
|
|
14
|
+
|
|
15
|
+
interface NamedColor {
|
|
16
|
+
hex: string;
|
|
17
|
+
name: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function asCss(items: NamedColor[]): string {
|
|
21
|
+
const body = items.map(({ hex, name }) => ` --${name}: ${hex};`).join("\n");
|
|
22
|
+
return `:root {\n${body}\n}\n`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function asJson(items: NamedColor[]): string {
|
|
26
|
+
const obj: Record<string, { hex: string; hsl: string; rgb: string }> = {};
|
|
27
|
+
for (const { hex, name } of items) {
|
|
28
|
+
const rgb = parseColor(hex)!;
|
|
29
|
+
const hsl = rgbToHsl(rgb);
|
|
30
|
+
obj[name] = {
|
|
31
|
+
hex,
|
|
32
|
+
hsl: `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`,
|
|
33
|
+
rgb: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return JSON.stringify(obj, null, 2) + "\n";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function asScss(items: NamedColor[]): string {
|
|
40
|
+
return items.map(({ hex, name }) => `$${name}: ${hex};`).join("\n") + "\n";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function asTailwind(items: NamedColor[], prefix?: string): string {
|
|
44
|
+
const key = prefix ?? "palette";
|
|
45
|
+
const lines = items
|
|
46
|
+
.map(({ hex, name }) => {
|
|
47
|
+
// Drop the prefix from the entry name if it was prefixed
|
|
48
|
+
const entry =
|
|
49
|
+
prefix && name.startsWith(`${prefix}-`)
|
|
50
|
+
? name.slice(prefix.length + 1)
|
|
51
|
+
: name;
|
|
52
|
+
return ` "${entry}": "${hex}",`;
|
|
53
|
+
})
|
|
54
|
+
.join("\n");
|
|
55
|
+
return `// tailwind.config.js
|
|
56
|
+
module.exports = {
|
|
57
|
+
theme: {
|
|
58
|
+
extend: {
|
|
59
|
+
colors: {
|
|
60
|
+
"${key}": {
|
|
61
|
+
${lines}
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function asTokens(items: NamedColor[], prefix?: string): string {
|
|
71
|
+
// W3C Design Tokens Community Group format (draft).
|
|
72
|
+
const group = prefix ?? "color";
|
|
73
|
+
const obj: Record<string, unknown> = {
|
|
74
|
+
[group]: Object.fromEntries(
|
|
75
|
+
items.map(({ hex, name }) => {
|
|
76
|
+
const entry =
|
|
77
|
+
prefix && name.startsWith(`${prefix}-`)
|
|
78
|
+
? name.slice(prefix.length + 1)
|
|
79
|
+
: name;
|
|
80
|
+
return [entry, { $type: "color", $value: hex }];
|
|
81
|
+
}),
|
|
82
|
+
),
|
|
83
|
+
};
|
|
84
|
+
return JSON.stringify(obj, null, 2) + "\n";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function defaultName(index: number): string {
|
|
88
|
+
// Tailwind-style 50/100…900 scale up to 10 colors, then numeric.
|
|
89
|
+
const scale = [
|
|
90
|
+
"50",
|
|
91
|
+
"100",
|
|
92
|
+
"200",
|
|
93
|
+
"300",
|
|
94
|
+
"400",
|
|
95
|
+
"500",
|
|
96
|
+
"600",
|
|
97
|
+
"700",
|
|
98
|
+
"800",
|
|
99
|
+
"900",
|
|
100
|
+
];
|
|
101
|
+
return scale[index] ?? String((index + 1) * 100);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function resolveNames(
|
|
105
|
+
colors: string[],
|
|
106
|
+
names?: string[],
|
|
107
|
+
prefix?: string,
|
|
108
|
+
): NamedColor[] {
|
|
109
|
+
return colors.map((c, i) => {
|
|
110
|
+
const parsed = parseColor(c);
|
|
111
|
+
if (!parsed) throw new Error(`Invalid color: ${c}`);
|
|
112
|
+
const hex = rgbToHex(parsed);
|
|
113
|
+
const raw = names?.[i] ?? defaultName(i);
|
|
114
|
+
const slug = raw.toLowerCase().replace(/[^a-z0-9-]+/g, "-");
|
|
115
|
+
const name = prefix ? `${prefix}-${slug}` : slug;
|
|
116
|
+
return { hex, name };
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const exportPaletteTool = {
|
|
121
|
+
description:
|
|
122
|
+
"Export a list of colors as CSS custom properties, SCSS variables, a Tailwind config snippet, W3C design tokens, or JSON.",
|
|
123
|
+
execute: async (args: {
|
|
124
|
+
colors: string[];
|
|
125
|
+
format: ExportFormat;
|
|
126
|
+
names?: string[];
|
|
127
|
+
prefix?: string;
|
|
128
|
+
}) => {
|
|
129
|
+
const { colors, format, names, prefix } = args;
|
|
130
|
+
|
|
131
|
+
if (names && names.length !== colors.length) {
|
|
132
|
+
return `Error: names array length (${names.length}) must match colors length (${colors.length}).`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let items: NamedColor[];
|
|
136
|
+
try {
|
|
137
|
+
items = resolveNames(colors, names, prefix);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
return e instanceof Error ? e.message : String(e);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
switch (format) {
|
|
143
|
+
case "css":
|
|
144
|
+
return asCss(items);
|
|
145
|
+
case "json":
|
|
146
|
+
return asJson(items);
|
|
147
|
+
case "scss":
|
|
148
|
+
return asScss(items);
|
|
149
|
+
case "tailwind":
|
|
150
|
+
return asTailwind(items, prefix);
|
|
151
|
+
case "tokens":
|
|
152
|
+
return asTokens(items, prefix);
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
name: "export_palette",
|
|
156
|
+
parameters: z.object({
|
|
157
|
+
colors: z.array(z.string()).min(1).describe("Palette colors"),
|
|
158
|
+
format: z
|
|
159
|
+
.enum(FORMATS)
|
|
160
|
+
.describe(
|
|
161
|
+
"Output format: css (custom properties), scss (variables), tailwind (config), tokens (W3C design tokens), json",
|
|
162
|
+
),
|
|
163
|
+
names: z
|
|
164
|
+
.array(z.string())
|
|
165
|
+
.optional()
|
|
166
|
+
.describe(
|
|
167
|
+
"Optional names for each color. Length must match colors. Defaults to a 50/100…900 scale.",
|
|
168
|
+
),
|
|
169
|
+
prefix: z
|
|
170
|
+
.string()
|
|
171
|
+
.optional()
|
|
172
|
+
.describe("Optional prefix for variable/token names (e.g. 'brand')"),
|
|
173
|
+
}),
|
|
174
|
+
};
|