@trishchuk/coolors-mcp 1.0.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/.claude/settings.local.json +39 -0
- package/.env +2 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +73 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +71 -0
- package/.github/pull_request_template.md +97 -0
- package/.github/workflows/ci.yml +127 -0
- package/.github/workflows/deploy-docs.yml +56 -0
- package/.github/workflows/release.yml +99 -0
- package/.mcp.json +12 -0
- package/.prettierignore +1 -0
- package/CLAUDE.md +201 -0
- package/DOCUMENTATION.md +274 -0
- package/GEMINI.md +54 -0
- package/LICENSE +21 -0
- package/README.md +401 -0
- package/demo/content_based_color.png +0 -0
- package/demo/music-player.html +621 -0
- package/demo/podcast-player.html +903 -0
- package/dist/bin/coolors-mcp.d.ts +1 -0
- package/dist/bin/coolors-mcp.js +154 -0
- package/dist/bin/coolors-mcp.js.map +1 -0
- package/dist/bin/server.d.ts +1 -0
- package/dist/bin/server.js +3292 -0
- package/dist/bin/server.js.map +1 -0
- package/dist/chunk-IQ7NN26V.js +114 -0
- package/dist/chunk-IQ7NN26V.js.map +1 -0
- package/dist/chunk-P3ARRKLS.js +1214 -0
- package/dist/chunk-P3ARRKLS.js.map +1 -0
- package/dist/color/index.d.ts +716 -0
- package/dist/color/index.js +153 -0
- package/dist/color/index.js.map +1 -0
- package/dist/coolors-mcp.d.ts +136 -0
- package/dist/coolors-mcp.js +7 -0
- package/dist/coolors-mcp.js.map +1 -0
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js +93 -0
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js.map +7 -0
- package/docs/.vitepress/cache/deps/_metadata.json +127 -0
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js +9 -0
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +7 -0
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +12683 -0
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js.map +7 -0
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +9719 -0
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js.map +7 -0
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +4710 -0
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +7 -0
- package/docs/.vitepress/cache/deps/cytoscape.js +30278 -0
- package/docs/.vitepress/cache/deps/cytoscape.js.map +7 -0
- package/docs/.vitepress/cache/deps/dayjs.js +285 -0
- package/docs/.vitepress/cache/deps/dayjs.js.map +7 -0
- package/docs/.vitepress/cache/deps/debug.js +468 -0
- package/docs/.vitepress/cache/deps/debug.js.map +7 -0
- package/docs/.vitepress/cache/deps/package.json +3 -0
- package/docs/.vitepress/cache/deps/prismjs.js +1466 -0
- package/docs/.vitepress/cache/deps/prismjs.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +228 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +142 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js +27 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js +65 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +53 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +73 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4507 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +584 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1146 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1667 -0
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js +1814 -0
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
- package/docs/.vitepress/cache/deps/vue.js +344 -0
- package/docs/.vitepress/cache/deps/vue.js.map +7 -0
- package/docs/.vitepress/components/ClientGrid.vue +125 -0
- package/docs/.vitepress/components/CodeBlock.vue +231 -0
- package/docs/.vitepress/components/ConfigModal.vue +477 -0
- package/docs/.vitepress/components/DiagramModal.vue +528 -0
- package/docs/.vitepress/components/TroubleshootingModal.vue +472 -0
- package/docs/.vitepress/config.js +162 -0
- package/docs/.vitepress/theme/FundingLayout.vue +251 -0
- package/docs/.vitepress/theme/Layout.vue +134 -0
- package/docs/.vitepress/theme/components/AdBanner.vue +317 -0
- package/docs/.vitepress/theme/components/AdPlaceholder.vue +78 -0
- package/docs/.vitepress/theme/components/FundingEffects.vue +322 -0
- package/docs/.vitepress/theme/components/FundingHero.vue +345 -0
- package/docs/.vitepress/theme/components/SupportSection.vue +511 -0
- package/docs/.vitepress/theme/custom-app.css +339 -0
- package/docs/.vitepress/theme/custom.css +699 -0
- package/docs/.vitepress/theme/index.js +25 -0
- package/docs/README.md +198 -0
- package/docs/concepts/accessibility.md +473 -0
- package/docs/concepts/color-spaces.md +222 -0
- package/docs/concepts/distance-metrics.md +384 -0
- package/docs/concepts/hct.md +261 -0
- package/docs/concepts/image-analysis.md +396 -0
- package/docs/concepts/material-design.md +306 -0
- package/docs/concepts/theme-matching.md +399 -0
- package/docs/examples/basic-colors.md +490 -0
- package/docs/examples/creating-themes.md +898 -0
- package/docs/examples/css-refactoring.md +824 -0
- package/docs/examples/image-extraction.md +882 -0
- package/docs/getting-started.md +366 -0
- package/docs/index.md +190 -0
- package/docs/installation.md +157 -0
- package/docs/tools/README.md +234 -0
- package/docs/tools/accessibility.md +614 -0
- package/docs/tools/color-operations.md +374 -0
- package/docs/tools/image-extraction.md +624 -0
- package/docs/tools/material-design.md +347 -0
- package/docs/tools/theme-matching.md +552 -0
- package/eslint.config.ts +14 -0
- package/examples/theme-matching.md +113 -0
- package/jsr.json +7 -0
- package/mcp-config.json +8 -0
- package/note.md +35 -0
- package/package.json +122 -0
- package/research_results.md +53 -0
- package/src/bin/coolors-mcp.ts +194 -0
- package/src/bin/server.ts +61 -0
- package/src/color/__tests__/conversions-argb.test.ts +198 -0
- package/src/color/__tests__/extract-colors.test.ts +360 -0
- package/src/color/__tests__/image-utils.test.ts +242 -0
- package/src/color/__tests__/reference-colors.test.ts +278 -0
- package/src/color/__tests__/round-trip.test.ts +197 -0
- package/src/color/conversions.test.ts +402 -0
- package/src/color/conversions.ts +393 -0
- package/src/color/dislike/__tests__/dislike-analyzer.test.ts +248 -0
- package/src/color/dislike/dislike-analyzer.ts +114 -0
- package/src/color/extract-colors.ts +228 -0
- package/src/color/hct/__tests__/hct-class.test.ts +232 -0
- package/src/color/hct/harmonization.ts +204 -0
- package/src/color/hct/hct-class.ts +109 -0
- package/src/color/hct/hct-solver.ts +168 -0
- package/src/color/hct/index.ts +39 -0
- package/src/color/hct/tonal-palette.ts +211 -0
- package/src/color/hct/types.ts +88 -0
- package/src/color/image-utils.ts +79 -0
- package/src/color/index.ts +87 -0
- package/src/color/material-theme.ts +157 -0
- package/src/color/metrics.test.ts +276 -0
- package/src/color/metrics.ts +281 -0
- package/src/color/quantize/__tests__/quantizer_celebi.test.ts +195 -0
- package/src/color/quantize/lab_point_provider.ts +55 -0
- package/src/color/quantize/point_provider.ts +27 -0
- package/src/color/quantize/quantizer_celebi.ts +51 -0
- package/src/color/quantize/quantizer_celebi_test.ts +71 -0
- package/src/color/quantize/quantizer_map.ts +47 -0
- package/src/color/quantize/quantizer_wsmeans.ts +232 -0
- package/src/color/quantize/quantizer_wu.ts +472 -0
- package/src/color/score/__tests__/score.test.ts +224 -0
- package/src/color/score/score.ts +175 -0
- package/src/color/types.ts +151 -0
- package/src/color/utils/color_utils.ts +292 -0
- package/src/color/utils/math_utils.ts +145 -0
- package/src/color/utils.test.ts +403 -0
- package/src/color/utils.ts +315 -0
- package/src/constants.ts +5 -0
- package/src/coolors-mcp.ts +37 -0
- package/src/examples/addition.ts +333 -0
- package/src/examples/color-demo.ts +125 -0
- package/src/examples/custom-logger.ts +201 -0
- package/src/examples/oauth-server.ts +113 -0
- package/src/examples/session-context.ts +269 -0
- package/src/session.ts +116 -0
- package/src/theme/__tests__/matcher.test.ts +180 -0
- package/src/theme/__tests__/parser.test.ts +148 -0
- package/src/theme/__tests__/refactor.test.ts +224 -0
- package/src/theme/index.ts +34 -0
- package/src/theme/matcher.ts +395 -0
- package/src/theme/parser.ts +392 -0
- package/src/theme/refactor.ts +360 -0
- package/src/theme/types.ts +152 -0
- package/src/tools/__tests__/gradient-generator.test.ts +206 -0
- package/src/tools/__tests__/palette-with-locks.test.ts +109 -0
- package/src/tools/color-conversion.tool.ts +54 -0
- package/src/tools/color-distance.tool.ts +41 -0
- package/src/tools/colors.ts +31 -0
- package/src/tools/contrast-checker.tool.ts +37 -0
- package/src/tools/dislike-analyzer.tool.ts +247 -0
- package/src/tools/gradient-generator.tool.ts +250 -0
- package/src/tools/image-extraction.tools.ts +289 -0
- package/src/tools/index.ts +39 -0
- package/src/tools/material-theme.tools.ts +250 -0
- package/src/tools/palette-generator.tool.ts +135 -0
- package/src/tools/palette-with-locks.tool.ts +221 -0
- package/src/tools/registry.ts +142 -0
- package/src/tools/simple-tools.ts +37 -0
- package/src/tools/theme-matching.tools.ts +334 -0
- package/src/types.ts +182 -0
- package/src/utils.ts +22 -0
- package/tsconfig.json +8 -0
- package/vitest.config.js +15 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import { Hct } from "../color/hct/index.js";
|
|
4
|
+
import {
|
|
5
|
+
hslToRgb,
|
|
6
|
+
labToRgb,
|
|
7
|
+
parseColor,
|
|
8
|
+
rgbToHex,
|
|
9
|
+
rgbToHsl,
|
|
10
|
+
rgbToLab,
|
|
11
|
+
} from "../color/index.js";
|
|
12
|
+
import { RGB } from "../color/types.js";
|
|
13
|
+
|
|
14
|
+
export const gradientGeneratorTool = {
|
|
15
|
+
description:
|
|
16
|
+
"Generate a smooth gradient between colors with multiple interpolation methods",
|
|
17
|
+
execute: async (args: {
|
|
18
|
+
colors: string[];
|
|
19
|
+
easing?: "ease-in-out" | "ease-in" | "ease-out" | "linear";
|
|
20
|
+
format?: "array" | "css-linear" | "css-radial" | "hex";
|
|
21
|
+
interpolation?: "hct" | "hsl" | "lab" | "lch" | "rgb";
|
|
22
|
+
steps: number;
|
|
23
|
+
}) => {
|
|
24
|
+
const {
|
|
25
|
+
colors,
|
|
26
|
+
easing = "linear",
|
|
27
|
+
format = "array",
|
|
28
|
+
interpolation = "lab",
|
|
29
|
+
steps,
|
|
30
|
+
} = args;
|
|
31
|
+
|
|
32
|
+
if (colors.length < 2) {
|
|
33
|
+
return "Error: At least 2 colors are required for a gradient";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Parse input colors
|
|
37
|
+
const parsedColors: RGB[] = [];
|
|
38
|
+
for (const color of colors) {
|
|
39
|
+
const parsed = parseColor(color);
|
|
40
|
+
if (!parsed) {
|
|
41
|
+
return `Invalid color format: ${color}`;
|
|
42
|
+
}
|
|
43
|
+
parsedColors.push(parsed);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Apply easing function
|
|
47
|
+
const applyEasing = (t: number): number => {
|
|
48
|
+
switch (easing) {
|
|
49
|
+
case "ease-in":
|
|
50
|
+
return t * t;
|
|
51
|
+
case "ease-in-out":
|
|
52
|
+
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
|
53
|
+
case "ease-out":
|
|
54
|
+
return t * (2 - t);
|
|
55
|
+
default:
|
|
56
|
+
return t;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Generate gradient colors
|
|
61
|
+
const gradient: RGB[] = [];
|
|
62
|
+
const segmentSteps = Math.floor(steps / (colors.length - 1));
|
|
63
|
+
const extraSteps = steps % (colors.length - 1);
|
|
64
|
+
|
|
65
|
+
for (let segment = 0; segment < colors.length - 1; segment++) {
|
|
66
|
+
const start = parsedColors[segment];
|
|
67
|
+
const end = parsedColors[segment + 1];
|
|
68
|
+
const currentSteps = segmentSteps + (segment < extraSteps ? 1 : 0);
|
|
69
|
+
|
|
70
|
+
for (let step = 0; step < currentSteps; step++) {
|
|
71
|
+
const t = applyEasing(step / currentSteps);
|
|
72
|
+
|
|
73
|
+
let interpolatedColor: RGB;
|
|
74
|
+
|
|
75
|
+
switch (interpolation) {
|
|
76
|
+
case "hct": {
|
|
77
|
+
// HCT interpolation (Material Design color space)
|
|
78
|
+
const startHct = Hct.fromInt(
|
|
79
|
+
(255 << 24) | (start.r << 16) | (start.g << 8) | start.b,
|
|
80
|
+
);
|
|
81
|
+
const endHct = Hct.fromInt(
|
|
82
|
+
(255 << 24) | (end.r << 16) | (end.g << 8) | end.b,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Interpolate hue (shortest path)
|
|
86
|
+
let hueDiff = endHct.hue - startHct.hue;
|
|
87
|
+
if (hueDiff > 180) hueDiff -= 360;
|
|
88
|
+
if (hueDiff < -180) hueDiff += 360;
|
|
89
|
+
|
|
90
|
+
const interpolatedHct = Hct.from(
|
|
91
|
+
(startHct.hue + hueDiff * t + 360) % 360,
|
|
92
|
+
startHct.chroma + (endHct.chroma - startHct.chroma) * t,
|
|
93
|
+
startHct.tone + (endHct.tone - startHct.tone) * t,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const argb = interpolatedHct.toInt();
|
|
97
|
+
interpolatedColor = {
|
|
98
|
+
b: argb & 0xff,
|
|
99
|
+
g: (argb >> 8) & 0xff,
|
|
100
|
+
r: (argb >> 16) & 0xff,
|
|
101
|
+
};
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
case "hsl": {
|
|
106
|
+
// HSL interpolation
|
|
107
|
+
const startHsl = rgbToHsl(start);
|
|
108
|
+
const endHsl = rgbToHsl(end);
|
|
109
|
+
|
|
110
|
+
// Handle hue interpolation (shortest path)
|
|
111
|
+
let hueDiff = endHsl.h - startHsl.h;
|
|
112
|
+
if (hueDiff > 180) hueDiff -= 360;
|
|
113
|
+
if (hueDiff < -180) hueDiff += 360;
|
|
114
|
+
|
|
115
|
+
interpolatedColor = hslToRgb({
|
|
116
|
+
h: (startHsl.h + hueDiff * t + 360) % 360,
|
|
117
|
+
l: startHsl.l + (endHsl.l - startHsl.l) * t,
|
|
118
|
+
s: startHsl.s + (endHsl.s - startHsl.s) * t,
|
|
119
|
+
});
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
case "lab": {
|
|
124
|
+
// LAB interpolation (perceptually uniform)
|
|
125
|
+
const startLab = rgbToLab(start);
|
|
126
|
+
const endLab = rgbToLab(end);
|
|
127
|
+
|
|
128
|
+
interpolatedColor = labToRgb({
|
|
129
|
+
a: startLab.a + (endLab.a - startLab.a) * t,
|
|
130
|
+
b: startLab.b + (endLab.b - startLab.b) * t,
|
|
131
|
+
l: startLab.l + (endLab.l - startLab.l) * t,
|
|
132
|
+
});
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
case "lch": {
|
|
137
|
+
// LCH interpolation (cylindrical LAB)
|
|
138
|
+
const startLab = rgbToLab(start);
|
|
139
|
+
const endLab = rgbToLab(end);
|
|
140
|
+
|
|
141
|
+
// Convert LAB to LCH
|
|
142
|
+
const startL = startLab.l;
|
|
143
|
+
const startC = Math.sqrt(
|
|
144
|
+
startLab.a * startLab.a + startLab.b * startLab.b,
|
|
145
|
+
);
|
|
146
|
+
const startH = (Math.atan2(startLab.b, startLab.a) * 180) / Math.PI;
|
|
147
|
+
|
|
148
|
+
const endL = endLab.l;
|
|
149
|
+
const endC = Math.sqrt(endLab.a * endLab.a + endLab.b * endLab.b);
|
|
150
|
+
const endH = (Math.atan2(endLab.b, endLab.a) * 180) / Math.PI;
|
|
151
|
+
|
|
152
|
+
// Interpolate in LCH
|
|
153
|
+
let hueDiff = endH - startH;
|
|
154
|
+
if (hueDiff > 180) hueDiff -= 360;
|
|
155
|
+
if (hueDiff < -180) hueDiff += 360;
|
|
156
|
+
|
|
157
|
+
const l = startL + (endL - startL) * t;
|
|
158
|
+
const c = startC + (endC - startC) * t;
|
|
159
|
+
const h = ((startH + hueDiff * t) * Math.PI) / 180;
|
|
160
|
+
|
|
161
|
+
// Convert back to LAB then RGB
|
|
162
|
+
interpolatedColor = labToRgb({
|
|
163
|
+
a: c * Math.cos(h),
|
|
164
|
+
b: c * Math.sin(h),
|
|
165
|
+
l: l,
|
|
166
|
+
});
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
case "rgb": {
|
|
171
|
+
// Simple RGB interpolation
|
|
172
|
+
interpolatedColor = {
|
|
173
|
+
b: Math.round(start.b + (end.b - start.b) * t),
|
|
174
|
+
g: Math.round(start.g + (end.g - start.g) * t),
|
|
175
|
+
r: Math.round(start.r + (end.r - start.r) * t),
|
|
176
|
+
};
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
default:
|
|
181
|
+
interpolatedColor = start; // Fallback
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
gradient.push(interpolatedColor);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Add the last color
|
|
189
|
+
gradient.push(parsedColors[parsedColors.length - 1]);
|
|
190
|
+
|
|
191
|
+
// Format output
|
|
192
|
+
const hexColors = gradient.map(rgbToHex);
|
|
193
|
+
|
|
194
|
+
switch (format) {
|
|
195
|
+
case "css-linear": {
|
|
196
|
+
const stops = hexColors.map((color, i) => {
|
|
197
|
+
const percent = (i / (hexColors.length - 1)) * 100;
|
|
198
|
+
return `${color} ${percent.toFixed(1)}%`;
|
|
199
|
+
});
|
|
200
|
+
return `linear-gradient(90deg, ${stops.join(", ")})`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
case "css-radial": {
|
|
204
|
+
const stops = hexColors.map((color, i) => {
|
|
205
|
+
const percent = (i / (hexColors.length - 1)) * 100;
|
|
206
|
+
return `${color} ${percent.toFixed(1)}%`;
|
|
207
|
+
});
|
|
208
|
+
return `radial-gradient(circle, ${stops.join(", ")})`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
case "hex":
|
|
212
|
+
return hexColors.join(", ");
|
|
213
|
+
|
|
214
|
+
case "array":
|
|
215
|
+
default:
|
|
216
|
+
return `Generated gradient with ${steps} steps:
|
|
217
|
+
${hexColors.map((color, i) => `${i + 1}. ${color}`).join("\n")}
|
|
218
|
+
|
|
219
|
+
Interpolation: ${interpolation}
|
|
220
|
+
Easing: ${easing}
|
|
221
|
+
Input colors: ${colors.join(" → ")}`;
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
name: "generate_gradient",
|
|
225
|
+
parameters: z.object({
|
|
226
|
+
colors: z
|
|
227
|
+
.array(z.string())
|
|
228
|
+
.min(2)
|
|
229
|
+
.describe("Colors to create gradient between (minimum 2)"),
|
|
230
|
+
easing: z
|
|
231
|
+
.enum(["linear", "ease-in", "ease-out", "ease-in-out"])
|
|
232
|
+
.default("linear")
|
|
233
|
+
.describe("Easing function for gradient transition"),
|
|
234
|
+
format: z
|
|
235
|
+
.enum(["hex", "css-linear", "css-radial", "array"])
|
|
236
|
+
.default("array")
|
|
237
|
+
.describe("Output format for the gradient"),
|
|
238
|
+
interpolation: z
|
|
239
|
+
.enum(["rgb", "hsl", "lab", "lch", "hct"])
|
|
240
|
+
.default("lab")
|
|
241
|
+
.describe(
|
|
242
|
+
"Color space for interpolation (lab/lch/hct are perceptually smooth)",
|
|
243
|
+
),
|
|
244
|
+
steps: z
|
|
245
|
+
.number()
|
|
246
|
+
.min(3)
|
|
247
|
+
.max(100)
|
|
248
|
+
.describe("Number of colors in the gradient"),
|
|
249
|
+
}),
|
|
250
|
+
};
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tools for image color extraction
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Tool } from "../types.js";
|
|
6
|
+
|
|
7
|
+
type ImageData = {
|
|
8
|
+
data: number[] | Uint8ClampedArray;
|
|
9
|
+
height: number;
|
|
10
|
+
width: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type McpTool = Tool<unknown>;
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
extractColors,
|
|
17
|
+
ExtractedColor,
|
|
18
|
+
extractThemePalette,
|
|
19
|
+
} from "../color/extract-colors.js";
|
|
20
|
+
import { generateMaterialTheme } from "../color/material-theme.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Extract dominant colors from image data
|
|
24
|
+
*/
|
|
25
|
+
export const extractImageColorsTool: McpTool = {
|
|
26
|
+
description:
|
|
27
|
+
"Extract dominant colors from an image. Input should be image data as an array of RGBA values.",
|
|
28
|
+
execute: async (args: unknown, _context: unknown) => {
|
|
29
|
+
const {
|
|
30
|
+
format = "json",
|
|
31
|
+
imageData,
|
|
32
|
+
maxColors = 5,
|
|
33
|
+
quality = "medium",
|
|
34
|
+
} = args as {
|
|
35
|
+
format?: string;
|
|
36
|
+
imageData: ImageData;
|
|
37
|
+
maxColors?: number;
|
|
38
|
+
quality?: "high" | "low" | "medium";
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
// Convert data array to Uint8ClampedArray if needed
|
|
43
|
+
const data =
|
|
44
|
+
imageData.data instanceof Uint8ClampedArray
|
|
45
|
+
? imageData.data
|
|
46
|
+
: new Uint8ClampedArray(imageData.data);
|
|
47
|
+
|
|
48
|
+
const processedImageData = {
|
|
49
|
+
data,
|
|
50
|
+
height: imageData.height,
|
|
51
|
+
width: imageData.width,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Extract colors
|
|
55
|
+
const colors = extractColors(processedImageData, {
|
|
56
|
+
filter: true,
|
|
57
|
+
maxColors,
|
|
58
|
+
quality,
|
|
59
|
+
scoringEnabled: true,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Format output based on requested format
|
|
63
|
+
switch (format) {
|
|
64
|
+
case "css":
|
|
65
|
+
return formatAsCSS(colors);
|
|
66
|
+
|
|
67
|
+
case "palette":
|
|
68
|
+
return formatAsPalette(colors);
|
|
69
|
+
|
|
70
|
+
case "json":
|
|
71
|
+
default:
|
|
72
|
+
return formatAsJSON(colors);
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return `Error extracting colors: ${error instanceof Error ? error.message : String(error)}`;
|
|
76
|
+
}
|
|
77
|
+
},
|
|
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
|
+
name: "extract_image_colors",
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Generate a Material Design theme from an image
|
|
126
|
+
*/
|
|
127
|
+
export const generateThemeFromImageTool: McpTool = {
|
|
128
|
+
description: "Generate a complete Material Design 3 theme from an image",
|
|
129
|
+
execute: async (args: unknown, _context: unknown) => {
|
|
130
|
+
const {
|
|
131
|
+
imageData,
|
|
132
|
+
includeCustomColors = true,
|
|
133
|
+
isDark = false,
|
|
134
|
+
} = args as {
|
|
135
|
+
imageData: ImageData;
|
|
136
|
+
includeCustomColors?: boolean;
|
|
137
|
+
isDark?: boolean;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
// Convert data array to Uint8ClampedArray if needed
|
|
142
|
+
const data =
|
|
143
|
+
imageData.data instanceof Uint8ClampedArray
|
|
144
|
+
? imageData.data
|
|
145
|
+
: new Uint8ClampedArray(imageData.data);
|
|
146
|
+
|
|
147
|
+
const processedImageData = {
|
|
148
|
+
data,
|
|
149
|
+
height: imageData.height,
|
|
150
|
+
width: imageData.width,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Extract theme palette
|
|
154
|
+
const palette = extractThemePalette(processedImageData);
|
|
155
|
+
|
|
156
|
+
// Convert primary color to hex for theme generation
|
|
157
|
+
const sourceColor = palette.primary.hex;
|
|
158
|
+
|
|
159
|
+
// Generate Material theme
|
|
160
|
+
const theme = generateMaterialTheme(sourceColor, { isDark });
|
|
161
|
+
|
|
162
|
+
// Add custom colors from extracted palette
|
|
163
|
+
if (includeCustomColors && palette.secondary) {
|
|
164
|
+
theme.customColors = {
|
|
165
|
+
secondary: {
|
|
166
|
+
color: palette.secondary.hex,
|
|
167
|
+
hct: palette.secondary.hct,
|
|
168
|
+
population: palette.secondary.population,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
if (palette.tertiary) {
|
|
173
|
+
theme.customColors.tertiary = {
|
|
174
|
+
color: palette.tertiary.hex,
|
|
175
|
+
hct: palette.tertiary.hct,
|
|
176
|
+
population: palette.tertiary.population,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Format output
|
|
182
|
+
let output = `# Material Design 3 Theme from Image\n\n`;
|
|
183
|
+
output += `## Source Colors\n`;
|
|
184
|
+
output += `- Primary: ${palette.primary.hex} (${palette.primary.percentage.toFixed(1)}%)\n`;
|
|
185
|
+
|
|
186
|
+
if (palette.secondary) {
|
|
187
|
+
output += `- Secondary: ${palette.secondary.hex} (${palette.secondary.percentage.toFixed(1)}%)\n`;
|
|
188
|
+
}
|
|
189
|
+
if (palette.tertiary) {
|
|
190
|
+
output += `- Tertiary: ${palette.tertiary.hex} (${palette.tertiary.percentage.toFixed(1)}%)\n`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
output += `\n## Theme Colors (${isDark ? "Dark" : "Light"} Mode)\n\n`;
|
|
194
|
+
output += `### Primary\n`;
|
|
195
|
+
output += `- primary: ${theme.schemes[isDark ? "dark" : "light"].primary}\n`;
|
|
196
|
+
output += `- onPrimary: ${theme.schemes[isDark ? "dark" : "light"].onPrimary}\n`;
|
|
197
|
+
output += `- primaryContainer: ${theme.schemes[isDark ? "dark" : "light"].primaryContainer}\n`;
|
|
198
|
+
output += `- onPrimaryContainer: ${theme.schemes[isDark ? "dark" : "light"].onPrimaryContainer}\n`;
|
|
199
|
+
|
|
200
|
+
output += `\n### Surface\n`;
|
|
201
|
+
output += `- surface: ${theme.schemes[isDark ? "dark" : "light"].surface}\n`;
|
|
202
|
+
output += `- onSurface: ${theme.schemes[isDark ? "dark" : "light"].onSurface}\n`;
|
|
203
|
+
output += `- surfaceVariant: ${theme.schemes[isDark ? "dark" : "light"].surfaceVariant}\n`;
|
|
204
|
+
output += `- onSurfaceVariant: ${theme.schemes[isDark ? "dark" : "light"].onSurfaceVariant}\n`;
|
|
205
|
+
|
|
206
|
+
output += `\n### Background\n`;
|
|
207
|
+
output += `- background: ${theme.schemes[isDark ? "dark" : "light"].background}\n`;
|
|
208
|
+
output += `- onBackground: ${theme.schemes[isDark ? "dark" : "light"].onBackground}\n`;
|
|
209
|
+
|
|
210
|
+
return output;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
return `Error generating theme: ${error instanceof Error ? error.message : String(error)}`;
|
|
213
|
+
}
|
|
214
|
+
},
|
|
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
|
+
name: "generate_theme_from_image",
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// Helper functions for formatting
|
|
253
|
+
|
|
254
|
+
function formatAsCSS(colors: ExtractedColor[]): string {
|
|
255
|
+
let css = ":root {\n";
|
|
256
|
+
colors.forEach((color, index) => {
|
|
257
|
+
css += ` --extracted-color-${index + 1}: ${color.hex}; /* ${color.percentage.toFixed(1)}% */\n`;
|
|
258
|
+
});
|
|
259
|
+
css += "}\n\n";
|
|
260
|
+
|
|
261
|
+
css += "/* Color Details */\n";
|
|
262
|
+
colors.forEach((color, index) => {
|
|
263
|
+
css += `/* Color ${index + 1}: ${color.hex}\n`;
|
|
264
|
+
css += ` RGB: rgb(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b})\n`;
|
|
265
|
+
css += ` HCT: hct(${color.hct.h.toFixed(1)}, ${color.hct.c.toFixed(1)}, ${color.hct.t.toFixed(1)})\n`;
|
|
266
|
+
css += ` Population: ${color.percentage.toFixed(1)}%\n`;
|
|
267
|
+
css += `*/\n\n`;
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return css;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function formatAsJSON(colors: ExtractedColor[]): string {
|
|
274
|
+
return JSON.stringify(colors, null, 2);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function formatAsPalette(colors: ExtractedColor[]): string {
|
|
278
|
+
let output = "# Extracted Color Palette\n\n";
|
|
279
|
+
|
|
280
|
+
colors.forEach((color, index) => {
|
|
281
|
+
output += `## Color ${index + 1}\n`;
|
|
282
|
+
output += `- Hex: ${color.hex}\n`;
|
|
283
|
+
output += `- RGB: rgb(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b})\n`;
|
|
284
|
+
output += `- HCT: H:${color.hct.h.toFixed(1)}° C:${color.hct.c.toFixed(1)} T:${color.hct.t.toFixed(1)}\n`;
|
|
285
|
+
output += `- Usage: ${color.percentage.toFixed(1)}%\n\n`;
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
return output;
|
|
289
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Tool Registry Index - Export all tools
|
|
2
|
+
|
|
3
|
+
// Color tools
|
|
4
|
+
export { colorConversionTool } from "./color-conversion.tool.js";
|
|
5
|
+
export { colorDistanceTool } from "./color-distance.tool.js";
|
|
6
|
+
// Legacy tools (if needed)
|
|
7
|
+
export { convertColor } from "./colors.js";
|
|
8
|
+
export { contrastCheckerTool } from "./contrast-checker.tool.js";
|
|
9
|
+
|
|
10
|
+
// Dislike analyzer tools
|
|
11
|
+
export {
|
|
12
|
+
analyzeColorLikabilityTool,
|
|
13
|
+
fixDislikedColorsBatchTool,
|
|
14
|
+
} from "./dislike-analyzer.tool.js";
|
|
15
|
+
|
|
16
|
+
export { gradientGeneratorTool } from "./gradient-generator.tool.js";
|
|
17
|
+
|
|
18
|
+
// Image extraction tools
|
|
19
|
+
export {
|
|
20
|
+
extractImageColorsTool,
|
|
21
|
+
generateThemeFromImageTool,
|
|
22
|
+
} from "./image-extraction.tools.js";
|
|
23
|
+
|
|
24
|
+
// Material Design tools
|
|
25
|
+
export {
|
|
26
|
+
generateMaterialThemeTool,
|
|
27
|
+
generateTonalPaletteTool,
|
|
28
|
+
harmonizeColorsTool,
|
|
29
|
+
} from "./material-theme.tools.js";
|
|
30
|
+
export { paletteGeneratorTool } from "./palette-generator.tool.js";
|
|
31
|
+
export { paletteWithLocksTool } from "./palette-with-locks.tool.js";
|
|
32
|
+
|
|
33
|
+
// Theme matching tools
|
|
34
|
+
export {
|
|
35
|
+
generateThemeCssTool,
|
|
36
|
+
matchThemeColorsBatchTool,
|
|
37
|
+
matchThemeColorTool,
|
|
38
|
+
refactorCssWithThemeTool,
|
|
39
|
+
} from "./theme-matching.tools.js";
|