@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.
Files changed (197) hide show
  1. package/.claude/settings.local.json +39 -0
  2. package/.env +2 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +73 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +71 -0
  5. package/.github/pull_request_template.md +97 -0
  6. package/.github/workflows/ci.yml +127 -0
  7. package/.github/workflows/deploy-docs.yml +56 -0
  8. package/.github/workflows/release.yml +99 -0
  9. package/.mcp.json +12 -0
  10. package/.prettierignore +1 -0
  11. package/CLAUDE.md +201 -0
  12. package/DOCUMENTATION.md +274 -0
  13. package/GEMINI.md +54 -0
  14. package/LICENSE +21 -0
  15. package/README.md +401 -0
  16. package/demo/content_based_color.png +0 -0
  17. package/demo/music-player.html +621 -0
  18. package/demo/podcast-player.html +903 -0
  19. package/dist/bin/coolors-mcp.d.ts +1 -0
  20. package/dist/bin/coolors-mcp.js +154 -0
  21. package/dist/bin/coolors-mcp.js.map +1 -0
  22. package/dist/bin/server.d.ts +1 -0
  23. package/dist/bin/server.js +3292 -0
  24. package/dist/bin/server.js.map +1 -0
  25. package/dist/chunk-IQ7NN26V.js +114 -0
  26. package/dist/chunk-IQ7NN26V.js.map +1 -0
  27. package/dist/chunk-P3ARRKLS.js +1214 -0
  28. package/dist/chunk-P3ARRKLS.js.map +1 -0
  29. package/dist/color/index.d.ts +716 -0
  30. package/dist/color/index.js +153 -0
  31. package/dist/color/index.js.map +1 -0
  32. package/dist/coolors-mcp.d.ts +136 -0
  33. package/dist/coolors-mcp.js +7 -0
  34. package/dist/coolors-mcp.js.map +1 -0
  35. package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js +93 -0
  36. package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js.map +7 -0
  37. package/docs/.vitepress/cache/deps/_metadata.json +127 -0
  38. package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js +9 -0
  39. package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +7 -0
  40. package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +12683 -0
  41. package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js.map +7 -0
  42. package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +9719 -0
  43. package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js.map +7 -0
  44. package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +4710 -0
  45. package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +7 -0
  46. package/docs/.vitepress/cache/deps/cytoscape.js +30278 -0
  47. package/docs/.vitepress/cache/deps/cytoscape.js.map +7 -0
  48. package/docs/.vitepress/cache/deps/dayjs.js +285 -0
  49. package/docs/.vitepress/cache/deps/dayjs.js.map +7 -0
  50. package/docs/.vitepress/cache/deps/debug.js +468 -0
  51. package/docs/.vitepress/cache/deps/debug.js.map +7 -0
  52. package/docs/.vitepress/cache/deps/package.json +3 -0
  53. package/docs/.vitepress/cache/deps/prismjs.js +1466 -0
  54. package/docs/.vitepress/cache/deps/prismjs.js.map +7 -0
  55. package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +228 -0
  56. package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js.map +7 -0
  57. package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +142 -0
  58. package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js.map +7 -0
  59. package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js +27 -0
  60. package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js.map +7 -0
  61. package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js +65 -0
  62. package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js.map +7 -0
  63. package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +53 -0
  64. package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js.map +7 -0
  65. package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +73 -0
  66. package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js.map +7 -0
  67. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4507 -0
  68. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
  69. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +584 -0
  70. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
  71. package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1146 -0
  72. package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
  73. package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1667 -0
  74. package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
  75. package/docs/.vitepress/cache/deps/vitepress___minisearch.js +1814 -0
  76. package/docs/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
  77. package/docs/.vitepress/cache/deps/vue.js +344 -0
  78. package/docs/.vitepress/cache/deps/vue.js.map +7 -0
  79. package/docs/.vitepress/components/ClientGrid.vue +125 -0
  80. package/docs/.vitepress/components/CodeBlock.vue +231 -0
  81. package/docs/.vitepress/components/ConfigModal.vue +477 -0
  82. package/docs/.vitepress/components/DiagramModal.vue +528 -0
  83. package/docs/.vitepress/components/TroubleshootingModal.vue +472 -0
  84. package/docs/.vitepress/config.js +162 -0
  85. package/docs/.vitepress/theme/FundingLayout.vue +251 -0
  86. package/docs/.vitepress/theme/Layout.vue +134 -0
  87. package/docs/.vitepress/theme/components/AdBanner.vue +317 -0
  88. package/docs/.vitepress/theme/components/AdPlaceholder.vue +78 -0
  89. package/docs/.vitepress/theme/components/FundingEffects.vue +322 -0
  90. package/docs/.vitepress/theme/components/FundingHero.vue +345 -0
  91. package/docs/.vitepress/theme/components/SupportSection.vue +511 -0
  92. package/docs/.vitepress/theme/custom-app.css +339 -0
  93. package/docs/.vitepress/theme/custom.css +699 -0
  94. package/docs/.vitepress/theme/index.js +25 -0
  95. package/docs/README.md +198 -0
  96. package/docs/concepts/accessibility.md +473 -0
  97. package/docs/concepts/color-spaces.md +222 -0
  98. package/docs/concepts/distance-metrics.md +384 -0
  99. package/docs/concepts/hct.md +261 -0
  100. package/docs/concepts/image-analysis.md +396 -0
  101. package/docs/concepts/material-design.md +306 -0
  102. package/docs/concepts/theme-matching.md +399 -0
  103. package/docs/examples/basic-colors.md +490 -0
  104. package/docs/examples/creating-themes.md +898 -0
  105. package/docs/examples/css-refactoring.md +824 -0
  106. package/docs/examples/image-extraction.md +882 -0
  107. package/docs/getting-started.md +366 -0
  108. package/docs/index.md +190 -0
  109. package/docs/installation.md +157 -0
  110. package/docs/tools/README.md +234 -0
  111. package/docs/tools/accessibility.md +614 -0
  112. package/docs/tools/color-operations.md +374 -0
  113. package/docs/tools/image-extraction.md +624 -0
  114. package/docs/tools/material-design.md +347 -0
  115. package/docs/tools/theme-matching.md +552 -0
  116. package/eslint.config.ts +14 -0
  117. package/examples/theme-matching.md +113 -0
  118. package/jsr.json +7 -0
  119. package/mcp-config.json +8 -0
  120. package/note.md +35 -0
  121. package/package.json +122 -0
  122. package/research_results.md +53 -0
  123. package/src/bin/coolors-mcp.ts +194 -0
  124. package/src/bin/server.ts +61 -0
  125. package/src/color/__tests__/conversions-argb.test.ts +198 -0
  126. package/src/color/__tests__/extract-colors.test.ts +360 -0
  127. package/src/color/__tests__/image-utils.test.ts +242 -0
  128. package/src/color/__tests__/reference-colors.test.ts +278 -0
  129. package/src/color/__tests__/round-trip.test.ts +197 -0
  130. package/src/color/conversions.test.ts +402 -0
  131. package/src/color/conversions.ts +393 -0
  132. package/src/color/dislike/__tests__/dislike-analyzer.test.ts +248 -0
  133. package/src/color/dislike/dislike-analyzer.ts +114 -0
  134. package/src/color/extract-colors.ts +228 -0
  135. package/src/color/hct/__tests__/hct-class.test.ts +232 -0
  136. package/src/color/hct/harmonization.ts +204 -0
  137. package/src/color/hct/hct-class.ts +109 -0
  138. package/src/color/hct/hct-solver.ts +168 -0
  139. package/src/color/hct/index.ts +39 -0
  140. package/src/color/hct/tonal-palette.ts +211 -0
  141. package/src/color/hct/types.ts +88 -0
  142. package/src/color/image-utils.ts +79 -0
  143. package/src/color/index.ts +87 -0
  144. package/src/color/material-theme.ts +157 -0
  145. package/src/color/metrics.test.ts +276 -0
  146. package/src/color/metrics.ts +281 -0
  147. package/src/color/quantize/__tests__/quantizer_celebi.test.ts +195 -0
  148. package/src/color/quantize/lab_point_provider.ts +55 -0
  149. package/src/color/quantize/point_provider.ts +27 -0
  150. package/src/color/quantize/quantizer_celebi.ts +51 -0
  151. package/src/color/quantize/quantizer_celebi_test.ts +71 -0
  152. package/src/color/quantize/quantizer_map.ts +47 -0
  153. package/src/color/quantize/quantizer_wsmeans.ts +232 -0
  154. package/src/color/quantize/quantizer_wu.ts +472 -0
  155. package/src/color/score/__tests__/score.test.ts +224 -0
  156. package/src/color/score/score.ts +175 -0
  157. package/src/color/types.ts +151 -0
  158. package/src/color/utils/color_utils.ts +292 -0
  159. package/src/color/utils/math_utils.ts +145 -0
  160. package/src/color/utils.test.ts +403 -0
  161. package/src/color/utils.ts +315 -0
  162. package/src/constants.ts +5 -0
  163. package/src/coolors-mcp.ts +37 -0
  164. package/src/examples/addition.ts +333 -0
  165. package/src/examples/color-demo.ts +125 -0
  166. package/src/examples/custom-logger.ts +201 -0
  167. package/src/examples/oauth-server.ts +113 -0
  168. package/src/examples/session-context.ts +269 -0
  169. package/src/session.ts +116 -0
  170. package/src/theme/__tests__/matcher.test.ts +180 -0
  171. package/src/theme/__tests__/parser.test.ts +148 -0
  172. package/src/theme/__tests__/refactor.test.ts +224 -0
  173. package/src/theme/index.ts +34 -0
  174. package/src/theme/matcher.ts +395 -0
  175. package/src/theme/parser.ts +392 -0
  176. package/src/theme/refactor.ts +360 -0
  177. package/src/theme/types.ts +152 -0
  178. package/src/tools/__tests__/gradient-generator.test.ts +206 -0
  179. package/src/tools/__tests__/palette-with-locks.test.ts +109 -0
  180. package/src/tools/color-conversion.tool.ts +54 -0
  181. package/src/tools/color-distance.tool.ts +41 -0
  182. package/src/tools/colors.ts +31 -0
  183. package/src/tools/contrast-checker.tool.ts +37 -0
  184. package/src/tools/dislike-analyzer.tool.ts +247 -0
  185. package/src/tools/gradient-generator.tool.ts +250 -0
  186. package/src/tools/image-extraction.tools.ts +289 -0
  187. package/src/tools/index.ts +39 -0
  188. package/src/tools/material-theme.tools.ts +250 -0
  189. package/src/tools/palette-generator.tool.ts +135 -0
  190. package/src/tools/palette-with-locks.tool.ts +221 -0
  191. package/src/tools/registry.ts +142 -0
  192. package/src/tools/simple-tools.ts +37 -0
  193. package/src/tools/theme-matching.tools.ts +334 -0
  194. package/src/types.ts +182 -0
  195. package/src/utils.ts +22 -0
  196. package/tsconfig.json +8 -0
  197. 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";