@trishchuk/coolors-mcp 1.0.0 → 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/ISSUE_TEMPLATE/bug_report.md +20 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +22 -8
- package/.github/pull_request_template.md +33 -8
- package/.github/workflows/ci.yml +107 -104
- package/.github/workflows/deploy-docs.yml +14 -11
- package/.github/workflows/release.yml +25 -23
- package/README.md +149 -15
- 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/docs/.vitepress/components/ClientGrid.vue +9 -3
- package/docs/.vitepress/components/CodeBlock.vue +51 -44
- package/docs/.vitepress/components/ConfigModal.vue +151 -67
- package/docs/.vitepress/components/DiagramModal.vue +186 -154
- package/docs/.vitepress/components/TroubleshootingModal.vue +101 -96
- package/docs/.vitepress/config.js +171 -141
- package/docs/.vitepress/theme/FundingLayout.vue +65 -54
- package/docs/.vitepress/theme/Layout.vue +21 -21
- package/docs/.vitepress/theme/components/AdBanner.vue +73 -52
- package/docs/.vitepress/theme/components/AdPlaceholder.vue +3 -3
- package/docs/.vitepress/theme/components/FundingEffects.vue +77 -53
- package/docs/.vitepress/theme/components/FundingHero.vue +78 -63
- package/docs/.vitepress/theme/components/SupportSection.vue +106 -89
- package/docs/.vitepress/theme/custom-app.css +19 -12
- package/docs/.vitepress/theme/custom.css +33 -25
- package/docs/.vitepress/theme/index.js +19 -16
- package/docs/concepts/accessibility.md +59 -47
- package/docs/concepts/color-spaces.md +28 -6
- package/docs/concepts/distance-metrics.md +45 -30
- package/docs/concepts/hct.md +30 -27
- package/docs/concepts/image-analysis.md +52 -21
- package/docs/concepts/material-design.md +43 -17
- package/docs/concepts/theme-matching.md +64 -40
- package/docs/examples/basic-colors.md +92 -108
- package/docs/examples/creating-themes.md +104 -108
- package/docs/examples/css-refactoring.md +33 -29
- package/docs/examples/image-extraction.md +145 -138
- package/docs/getting-started.md +45 -34
- package/docs/index.md +5 -1
- package/docs/installation.md +15 -1
- package/docs/tools/accessibility.md +74 -68
- package/docs/tools/image-extraction.md +62 -54
- package/docs/tools/theme-matching.md +45 -42
- package/eslint.config.ts +13 -0
- package/jsr.json +1 -1
- package/package.json +17 -13
- 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 -39
- 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/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 -93
- 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 -9
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +0 -7
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +0 -12683
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js.map +0 -7
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +0 -9719
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js.map +0 -7
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +0 -4710
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +0 -7
- package/docs/.vitepress/cache/deps/cytoscape.js +0 -30278
- package/docs/.vitepress/cache/deps/cytoscape.js.map +0 -7
- package/docs/.vitepress/cache/deps/dayjs.js +0 -285
- package/docs/.vitepress/cache/deps/dayjs.js.map +0 -7
- package/docs/.vitepress/cache/deps/debug.js +0 -468
- 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 -1466
- package/docs/.vitepress/cache/deps/prismjs.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +0 -228
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +0 -142
- 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 -65
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +0 -53
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +0 -73
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4507
- 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 -1146
- 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 -1667
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js +0 -1814
- 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 -35
- 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
|
@@ -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
|
+
};
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
rgbToHsl,
|
|
10
10
|
rgbToLab,
|
|
11
11
|
} from "../color/index.js";
|
|
12
|
-
import {
|
|
12
|
+
import { RGB } from "../color/types.js";
|
|
13
13
|
|
|
14
14
|
export const paletteWithLocksTool = {
|
|
15
15
|
description:
|
|
@@ -64,7 +64,9 @@ export const paletteWithLocksTool = {
|
|
|
64
64
|
|
|
65
65
|
let minDistance = Infinity;
|
|
66
66
|
for (const locked of parsedLocked) {
|
|
67
|
-
const dist = colorDistance(candidate, locked,
|
|
67
|
+
const dist = colorDistance(candidate, locked, {
|
|
68
|
+
metric: "deltaE2000",
|
|
69
|
+
});
|
|
68
70
|
if (dist < minDistance) minDistance = dist;
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -135,17 +137,15 @@ export const paletteWithLocksTool = {
|
|
|
135
137
|
case "harmony": {
|
|
136
138
|
// Generate harmonious colors based on locked colors
|
|
137
139
|
const baseHsl = rgbToHsl(parsedLocked[0]);
|
|
138
|
-
const hueStep = 360 / totalColors;
|
|
139
140
|
|
|
140
141
|
for (let i = 0; i < remainingSlots; i++) {
|
|
141
|
-
let newHue = baseHsl.h;
|
|
142
142
|
let attempts = 0;
|
|
143
143
|
let bestColor: null | RGB = null;
|
|
144
144
|
let maxMinDistance = 0;
|
|
145
145
|
|
|
146
146
|
// Try different hues to find one that's not too close to locked colors
|
|
147
147
|
while (attempts < 36) {
|
|
148
|
-
newHue = (baseHsl.h + attempts * 10) % 360;
|
|
148
|
+
const newHue = (baseHsl.h + attempts * 10) % 360;
|
|
149
149
|
const candidate = hslToRgb({
|
|
150
150
|
h: newHue,
|
|
151
151
|
l: baseHsl.l + (Math.random() - 0.5) * 20,
|
|
@@ -155,7 +155,9 @@ export const paletteWithLocksTool = {
|
|
|
155
155
|
// Check minimum distance to all locked colors
|
|
156
156
|
let minDistance = Infinity;
|
|
157
157
|
for (const locked of parsedLocked) {
|
|
158
|
-
const dist = colorDistance(candidate, locked,
|
|
158
|
+
const dist = colorDistance(candidate, locked, {
|
|
159
|
+
metric: "deltaE2000",
|
|
160
|
+
});
|
|
159
161
|
if (dist < minDistance) minDistance = dist;
|
|
160
162
|
}
|
|
161
163
|
|
package/src/types.ts
CHANGED
|
@@ -36,8 +36,8 @@ export type Context<T extends CoolorsMCPSessionAuth> = {
|
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
export type CoolorsMCPEvents = {
|
|
39
|
-
connect: (event: { session:
|
|
40
|
-
disconnect: (event: { session:
|
|
39
|
+
connect: (event: { session: unknown }) => void;
|
|
40
|
+
disconnect: (event: { session: unknown }) => void;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
export type CoolorsMCPSessionAuth = Record<string, unknown> | undefined;
|
|
@@ -164,7 +164,6 @@ export type Tool<
|
|
|
164
164
|
| ResourceLink
|
|
165
165
|
| string
|
|
166
166
|
| TextContent
|
|
167
|
-
| void
|
|
168
167
|
>;
|
|
169
168
|
name: string;
|
|
170
169
|
parameters?: Params;
|
package/tsconfig.json
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"extends": "@tsconfig/node22/tsconfig.json",
|
|
3
3
|
"compilerOptions": {
|
|
4
|
+
"ignoreDeprecations": "6.0",
|
|
4
5
|
"noEmit": true,
|
|
5
6
|
"noUnusedLocals": true,
|
|
6
|
-
"noUnusedParameters": true
|
|
7
|
-
|
|
7
|
+
"noUnusedParameters": true,
|
|
8
|
+
"types": ["node"]
|
|
9
|
+
},
|
|
10
|
+
"exclude": [
|
|
11
|
+
"dist",
|
|
12
|
+
"node_modules",
|
|
13
|
+
"src/examples",
|
|
14
|
+
"**/__tests__/**",
|
|
15
|
+
"**/*.test.ts",
|
|
16
|
+
"**/*_test.ts"
|
|
17
|
+
]
|
|
8
18
|
}
|
package/vitest.config.js
CHANGED
|
@@ -2,9 +2,7 @@ import { defineConfig } from "vitest/config";
|
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
4
|
test: {
|
|
5
|
-
|
|
6
|
-
forks: { execArgv: ["--experimental-eventsource"] },
|
|
7
|
-
},
|
|
5
|
+
forks: { execArgv: ["--experimental-eventsource"] },
|
|
8
6
|
exclude: [
|
|
9
7
|
"**/node_modules/**",
|
|
10
8
|
"**/dist/**",
|