@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.
Files changed (140) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +20 -8
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +22 -8
  3. package/.github/pull_request_template.md +33 -8
  4. package/.github/workflows/ci.yml +107 -104
  5. package/.github/workflows/deploy-docs.yml +14 -11
  6. package/.github/workflows/release.yml +25 -23
  7. package/README.md +149 -15
  8. package/dist/bin/server.js +997 -256
  9. package/dist/bin/server.js.map +1 -1
  10. package/dist/{chunk-P3ARRKLS.js → chunk-HOMDMKUY.js} +3 -1
  11. package/dist/{chunk-P3ARRKLS.js.map → chunk-HOMDMKUY.js.map} +1 -1
  12. package/dist/{chunk-IQ7NN26V.js → chunk-LHW2ZTOU.js} +14 -2
  13. package/dist/chunk-LHW2ZTOU.js.map +1 -0
  14. package/dist/color/index.js +1 -1
  15. package/dist/coolors-mcp.d.ts +4 -4
  16. package/dist/coolors-mcp.js +1 -1
  17. package/docs/.vitepress/components/ClientGrid.vue +9 -3
  18. package/docs/.vitepress/components/CodeBlock.vue +51 -44
  19. package/docs/.vitepress/components/ConfigModal.vue +151 -67
  20. package/docs/.vitepress/components/DiagramModal.vue +186 -154
  21. package/docs/.vitepress/components/TroubleshootingModal.vue +101 -96
  22. package/docs/.vitepress/config.js +171 -141
  23. package/docs/.vitepress/theme/FundingLayout.vue +65 -54
  24. package/docs/.vitepress/theme/Layout.vue +21 -21
  25. package/docs/.vitepress/theme/components/AdBanner.vue +73 -52
  26. package/docs/.vitepress/theme/components/AdPlaceholder.vue +3 -3
  27. package/docs/.vitepress/theme/components/FundingEffects.vue +77 -53
  28. package/docs/.vitepress/theme/components/FundingHero.vue +78 -63
  29. package/docs/.vitepress/theme/components/SupportSection.vue +106 -89
  30. package/docs/.vitepress/theme/custom-app.css +19 -12
  31. package/docs/.vitepress/theme/custom.css +33 -25
  32. package/docs/.vitepress/theme/index.js +19 -16
  33. package/docs/concepts/accessibility.md +59 -47
  34. package/docs/concepts/color-spaces.md +28 -6
  35. package/docs/concepts/distance-metrics.md +45 -30
  36. package/docs/concepts/hct.md +30 -27
  37. package/docs/concepts/image-analysis.md +52 -21
  38. package/docs/concepts/material-design.md +43 -17
  39. package/docs/concepts/theme-matching.md +64 -40
  40. package/docs/examples/basic-colors.md +92 -108
  41. package/docs/examples/creating-themes.md +104 -108
  42. package/docs/examples/css-refactoring.md +33 -29
  43. package/docs/examples/image-extraction.md +145 -138
  44. package/docs/getting-started.md +45 -34
  45. package/docs/index.md +5 -1
  46. package/docs/installation.md +15 -1
  47. package/docs/tools/accessibility.md +74 -68
  48. package/docs/tools/image-extraction.md +62 -54
  49. package/docs/tools/theme-matching.md +45 -42
  50. package/eslint.config.ts +13 -0
  51. package/jsr.json +1 -1
  52. package/package.json +17 -13
  53. package/src/bin/server.ts +13 -1
  54. package/src/color/__tests__/extract-colors.test.ts +20 -30
  55. package/src/color/apca.ts +105 -0
  56. package/src/color/color-blindness.ts +109 -0
  57. package/src/coolors-mcp.ts +1 -1
  58. package/src/session.ts +10 -2
  59. package/src/theme/matcher.ts +1 -1
  60. package/src/theme/refactor.ts +1 -1
  61. package/src/theme/types.ts +3 -0
  62. package/src/tools/__tests__/cohesion.test.ts +97 -0
  63. package/src/tools/__tests__/color-blindness.test.ts +45 -0
  64. package/src/tools/__tests__/color-conversion.test.ts +38 -0
  65. package/src/tools/__tests__/contrast-checker.test.ts +56 -0
  66. package/src/tools/__tests__/palette-export.test.ts +54 -0
  67. package/src/tools/adjust-color.tool.ts +80 -0
  68. package/src/tools/cohesion.tools.ts +380 -0
  69. package/src/tools/color-blindness.tool.ts +168 -0
  70. package/src/tools/color-conversion.tool.ts +1 -1
  71. package/src/tools/contrast-checker.tool.ts +53 -14
  72. package/src/tools/dislike-analyzer.tool.ts +41 -54
  73. package/src/tools/image-extraction.tools.ts +62 -115
  74. package/src/tools/index.ts +15 -2
  75. package/src/tools/palette-export.tool.ts +174 -0
  76. package/src/tools/palette-with-locks.tool.ts +8 -6
  77. package/src/types.ts +2 -3
  78. package/tsconfig.json +12 -2
  79. package/vitest.config.js +1 -3
  80. package/.claude/settings.local.json +0 -39
  81. package/.env +0 -2
  82. package/.mcp.json +0 -12
  83. package/CLAUDE.md +0 -201
  84. package/DOCUMENTATION.md +0 -274
  85. package/GEMINI.md +0 -54
  86. package/demo/content_based_color.png +0 -0
  87. package/demo/music-player.html +0 -621
  88. package/demo/podcast-player.html +0 -903
  89. package/dist/chunk-IQ7NN26V.js.map +0 -1
  90. package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js +0 -93
  91. package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js.map +0 -7
  92. package/docs/.vitepress/cache/deps/_metadata.json +0 -127
  93. package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js +0 -9
  94. package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +0 -7
  95. package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +0 -12683
  96. package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js.map +0 -7
  97. package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +0 -9719
  98. package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js.map +0 -7
  99. package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +0 -4710
  100. package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +0 -7
  101. package/docs/.vitepress/cache/deps/cytoscape.js +0 -30278
  102. package/docs/.vitepress/cache/deps/cytoscape.js.map +0 -7
  103. package/docs/.vitepress/cache/deps/dayjs.js +0 -285
  104. package/docs/.vitepress/cache/deps/dayjs.js.map +0 -7
  105. package/docs/.vitepress/cache/deps/debug.js +0 -468
  106. package/docs/.vitepress/cache/deps/debug.js.map +0 -7
  107. package/docs/.vitepress/cache/deps/package.json +0 -3
  108. package/docs/.vitepress/cache/deps/prismjs.js +0 -1466
  109. package/docs/.vitepress/cache/deps/prismjs.js.map +0 -7
  110. package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +0 -228
  111. package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js.map +0 -7
  112. package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +0 -142
  113. package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js.map +0 -7
  114. package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js +0 -27
  115. package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js.map +0 -7
  116. package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js +0 -65
  117. package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js.map +0 -7
  118. package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +0 -53
  119. package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js.map +0 -7
  120. package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +0 -73
  121. package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js.map +0 -7
  122. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4507
  123. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
  124. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -584
  125. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
  126. package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +0 -1146
  127. package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +0 -7
  128. package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +0 -1667
  129. package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +0 -7
  130. package/docs/.vitepress/cache/deps/vitepress___minisearch.js +0 -1814
  131. package/docs/.vitepress/cache/deps/vitepress___minisearch.js.map +0 -7
  132. package/docs/.vitepress/cache/deps/vue.js +0 -344
  133. package/docs/.vitepress/cache/deps/vue.js.map +0 -7
  134. package/examples/theme-matching.md +0 -113
  135. package/mcp-config.json +0 -8
  136. package/note.md +0 -35
  137. package/research_results.md +0 -53
  138. package/src/tools/colors.ts +0 -31
  139. package/src/tools/registry.ts +0 -142
  140. package/src/tools/simple-tools.ts +0 -37
@@ -8,24 +8,24 @@ Find the closest matching theme variable for a given color.
8
8
 
9
9
  ### Parameters
10
10
 
11
- | Parameter | Type | Required | Description |
12
- |-----------|------|----------|-------------|
13
- | `color` | string | ✅ | Color to match (hex, rgb, hsl) |
14
- | `themeCSS` | string | ✅ | CSS containing theme variables |
15
- | `context` | string | ❌ | Usage context: text, background, border, shadow, accent, decorative |
16
- | `minConfidence` | number | ❌ | Minimum confidence threshold (0-100, default: 70) |
11
+ | Parameter | Type | Required | Description |
12
+ | --------------- | ------ | -------- | ------------------------------------------------------------------- |
13
+ | `color` | string | ✅ | Color to match (hex, rgb, hsl) |
14
+ | `themeCSS` | string | ✅ | CSS containing theme variables |
15
+ | `context` | string | ❌ | Usage context: text, background, border, shadow, accent, decorative |
16
+ | `minConfidence` | number | ❌ | Minimum confidence threshold (0-100, default: 70) |
17
17
 
18
18
  ### Returns
19
19
 
20
20
  ```typescript
21
21
  {
22
- match: string; // CSS variable name
23
- confidence: number; // Confidence score (0-100)
22
+ match: string; // CSS variable name
23
+ confidence: number; // Confidence score (0-100)
24
24
  originalColor: string; // Input color
25
- themeColor: string; // Matched theme color
26
- distance: number; // Perceptual distance
25
+ themeColor: string; // Matched theme color
26
+ distance: number; // Perceptual distance
27
27
  semantic: {
28
- role: string; // Detected semantic role
28
+ role: string; // Detected semantic role
29
29
  compatible: boolean; // Context compatibility
30
30
  }
31
31
  }
@@ -117,13 +117,13 @@ Automatically replace hardcoded colors with theme variables in CSS.
117
117
 
118
118
  ### Parameters
119
119
 
120
- | Parameter | Type | Required | Description |
121
- |-----------|------|----------|-------------|
122
- | `css` | string | ✅ | CSS content to refactor |
123
- | `themeCSS` | string | ✅ | CSS containing theme variables |
124
- | `minConfidence` | number | ❌ | Minimum confidence for replacements (default: 70) |
125
- | `preserveOriginal` | boolean | ❌ | Keep original values as comments (default: true) |
126
- | `generateReport` | boolean | ❌ | Generate detailed refactoring report (default: false) |
120
+ | Parameter | Type | Required | Description |
121
+ | ------------------ | ------- | -------- | ----------------------------------------------------- |
122
+ | `css` | string | ✅ | CSS content to refactor |
123
+ | `themeCSS` | string | ✅ | CSS containing theme variables |
124
+ | `minConfidence` | number | ❌ | Minimum confidence for replacements (default: 70) |
125
+ | `preserveOriginal` | boolean | ❌ | Keep original values as comments (default: true) |
126
+ | `generateReport` | boolean | ❌ | Generate detailed refactoring report (default: false) |
127
127
 
128
128
  ### Returns
129
129
 
@@ -236,26 +236,26 @@ Match multiple colors to theme variables in a single operation.
236
236
 
237
237
  ### Parameters
238
238
 
239
- | Parameter | Type | Required | Description |
240
- |-----------|------|----------|-------------|
241
- | `colors` | string[] | ✅ | Array of colors to match (max 50) |
242
- | `themeCSS` | string | ✅ | CSS containing theme variables |
243
- | `context` | string | ❌ | Usage context for all colors |
239
+ | Parameter | Type | Required | Description |
240
+ | ---------- | -------- | -------- | --------------------------------- |
241
+ | `colors` | string[] | ✅ | Array of colors to match (max 50) |
242
+ | `themeCSS` | string | ✅ | CSS containing theme variables |
243
+ | `context` | string | ❌ | Usage context for all colors |
244
244
 
245
245
  ### Returns
246
246
 
247
247
  ```typescript
248
248
  {
249
249
  matches: Array<{
250
- color: string; // Input color
250
+ color: string; // Input color
251
251
  match: string | null; // Matched variable or null
252
- confidence: number; // Match confidence
253
- distance: number; // Perceptual distance
252
+ confidence: number; // Match confidence
253
+ distance: number; // Perceptual distance
254
254
  }>;
255
255
  summary: {
256
- total: number; // Total colors processed
257
- matched: number; // Successfully matched
258
- unmatched: number; // No suitable match
256
+ total: number; // Total colors processed
257
+ matched: number; // Successfully matched
258
+ unmatched: number; // No suitable match
259
259
  averageConfidence: number;
260
260
  }
261
261
  }
@@ -335,11 +335,11 @@ Generate CSS custom properties for a complete theme from a source color.
335
335
 
336
336
  ### Parameters
337
337
 
338
- | Parameter | Type | Required | Description |
339
- |-----------|------|----------|-------------|
340
- | `sourceColor` | string | ✅ | Source color for theme generation |
341
- | `prefix` | string | ❌ | Prefix for CSS variables (default: "color") |
342
- | `includeTones` | number[] | ❌ | Tone values to include (default: Material Design tones) |
338
+ | Parameter | Type | Required | Description |
339
+ | -------------- | -------- | -------- | ------------------------------------------------------- |
340
+ | `sourceColor` | string | ✅ | Source color for theme generation |
341
+ | `prefix` | string | ❌ | Prefix for CSS variables (default: "color") |
342
+ | `includeTones` | number[] | ❌ | Tone values to include (default: Material Design tones) |
343
343
 
344
344
  ### Returns
345
345
 
@@ -440,11 +440,11 @@ Use clear, semantic names:
440
440
 
441
441
  ### Confidence Thresholds
442
442
 
443
- | Use Case | Recommended | Rationale |
444
- |----------|-------------|-----------|
445
- | Production | 85-100% | Avoid false matches |
446
- | Development | 70-85% | Balance accuracy/coverage |
447
- | Exploration | 50-70% | Find possibilities |
443
+ | Use Case | Recommended | Rationale |
444
+ | ----------- | ----------- | ------------------------- |
445
+ | Production | 85-100% | Avoid false matches |
446
+ | Development | 70-85% | Balance accuracy/coverage |
447
+ | Exploration | 50-70% | Find possibilities |
448
448
 
449
449
  ### Performance Tips
450
450
 
@@ -466,7 +466,7 @@ if (result.confidence < minConfidence) {
466
466
  try {
467
467
  const refactored = await refactorCSS(css, theme);
468
468
  } catch (error) {
469
- console.error('Invalid CSS:', error);
469
+ console.error("Invalid CSS:", error);
470
470
  }
471
471
  ```
472
472
 
@@ -498,7 +498,7 @@ if (!result) {
498
498
  const colors = extractThemeColors(themeCSS);
499
499
  const unique = new Set(colors.values());
500
500
  if (unique.size < colors.size) {
501
- console.warn('Duplicate colors in theme');
501
+ console.warn("Duplicate colors in theme");
502
502
  }
503
503
  ```
504
504
 
@@ -524,6 +524,7 @@ const refactored = refactorWithTheme(css, theme);
524
524
 
525
525
  **Problem**: All matches have low confidence
526
526
  **Solution**:
527
+
527
528
  - Check theme has enough color variations
528
529
  - Lower minConfidence threshold
529
530
  - Add more theme variables
@@ -532,6 +533,7 @@ const refactored = refactorWithTheme(css, theme);
532
533
 
533
534
  **Problem**: Colors match wrong semantic role
534
535
  **Solution**:
536
+
535
537
  - Use clearer variable naming
536
538
  - Provide context parameter
537
539
  - Adjust semantic weight in matching
@@ -540,6 +542,7 @@ const refactored = refactorWithTheme(css, theme);
540
542
 
541
543
  **Problem**: Slow with large CSS files
542
544
  **Solution**:
545
+
543
546
  - Split CSS into smaller chunks
544
547
  - Use batch operations
545
548
  - Cache parsed theme variables
@@ -549,4 +552,4 @@ const refactored = refactorWithTheme(css, theme);
549
552
  - [Theme Matching Concepts](../concepts/theme-matching.md) - How matching works
550
553
  - [CSS Refactoring Examples](../examples/css-refactoring.md) - Practical examples
551
554
  - [Material Design Tools](./material-design.md) - Theme generation
552
- - [Color Operations](./color-operations.md) - Basic color tools
555
+ - [Color Operations](./color-operations.md) - Basic color tools
package/eslint.config.ts CHANGED
@@ -11,4 +11,17 @@ export default tseslint.config(
11
11
  {
12
12
  ignores: ["**/*.js", "dist/**"],
13
13
  },
14
+ {
15
+ // Vendored Material Color Utilities use the "declare-then-assign-in-loop"
16
+ // pattern liberally; that conflicts with ESLint 10's new no-useless-assignment.
17
+ files: [
18
+ "src/color/quantize/**",
19
+ "src/color/utils/color_utils.ts",
20
+ "src/color/hct/**",
21
+ "src/color/score/**",
22
+ ],
23
+ rules: {
24
+ "no-useless-assignment": "off",
25
+ },
26
+ },
14
27
  );
package/jsr.json CHANGED
@@ -3,5 +3,5 @@
3
3
  "include": ["src/CoolorsMCP.ts", "src/bin/coolors-mcp.ts"],
4
4
  "license": "MIT",
5
5
  "name": "@punkpeye/coolors-mcp",
6
- "version": "1.0.0"
6
+ "version": "1.1.0"
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trishchuk/coolors-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "main": "dist/CoolorsMCP.js",
5
5
  "scripts": {
6
6
  "build": "tsup",
@@ -34,6 +34,12 @@
34
34
  "image-extraction"
35
35
  ],
36
36
  "type": "module",
37
+ "packageManager": "pnpm@10.30.3",
38
+ "pnpm": {
39
+ "onlyBuiltDependencies": [
40
+ "esbuild"
41
+ ]
42
+ },
37
43
  "author": "Taras Trishchuk <x51xxx@gmail.com>",
38
44
  "license": "MIT",
39
45
  "description": "Advanced color operations MCP server with Material Design 3 support, CSS theme matching, image color extraction, and accessibility compliance. Features HCT color space for perceptually accurate operations.",
@@ -42,22 +48,20 @@
42
48
  "dependencies": {
43
49
  "@modelcontextprotocol/sdk": "^1.17.2",
44
50
  "@standard-schema/spec": "^1.0.0",
45
- "culori": "^4.0.2",
46
51
  "execa": "^9.6.0",
47
52
  "file-type": "^21.0.0",
48
53
  "fuse.js": "^7.1.0",
49
- "mcp-proxy": "^5.5.4",
54
+ "mcp-proxy": "^6.5.1",
50
55
  "strict-event-emitter-types": "^2.0.0",
51
- "undici": "^7.13.0",
56
+ "undici": "^8.3.0",
52
57
  "uri-templates": "^0.2.0",
53
58
  "xsschema": "0.3.5",
54
59
  "yargs": "^18.0.0",
55
- "zod": "^3.25.76",
56
- "zod-to-json-schema": "^3.24.6"
60
+ "zod": "^4.4.3"
57
61
  },
58
62
  "repository": {
59
63
  "type": "git",
60
- "url": "https://github.com/x51xxx/coolors-mcp"
64
+ "url": "git+https://github.com/x51xxx/coolors-mcp.git"
61
65
  },
62
66
  "homepage": "https://github.com/x51xxx/coolors-mcp",
63
67
  "bugs": {
@@ -76,7 +80,7 @@
76
80
  ]
77
81
  },
78
82
  "devDependencies": {
79
- "@eslint/js": "^9.33.0",
83
+ "@eslint/js": "^10.0.1",
80
84
  "@modelcontextprotocol/inspector": "^0.16.2",
81
85
  "@sebbo2002/semantic-release-jsr": "^3.0.1",
82
86
  "@tsconfig/node22": "^22.0.2",
@@ -84,9 +88,9 @@
84
88
  "@types/uri-templates": "^0.1.34",
85
89
  "@types/yargs": "^17.0.33",
86
90
  "@valibot/to-json-schema": "^1.3.0",
87
- "@wong2/mcp-cli": "^1.13.0",
91
+ "@wong2/mcp-cli": "^2.0.0",
88
92
  "arktype": "^2.1.20",
89
- "eslint": "^9.33.0",
93
+ "eslint": "^10.4.0",
90
94
  "eslint-config-prettier": "^10.1.8",
91
95
  "eslint-plugin-perfectionist": "^4.15.0",
92
96
  "eslint-plugin-prettier": "^5.5.4",
@@ -95,14 +99,14 @@
95
99
  "jiti": "^2.5.1",
96
100
  "jsr": "^0.13.5",
97
101
  "prettier": "^3.6.2",
98
- "semantic-release": "^24.2.7",
102
+ "semantic-release": "^25.0.3",
99
103
  "tsup": "^8.5.0",
100
- "typescript": "^5.9.2",
104
+ "typescript": "^6.0.3",
101
105
  "typescript-eslint": "^8.39.0",
102
106
  "valibot": "^1.1.0",
103
107
  "vitepress": "^1.6.4",
104
108
  "vitepress-plugin-mermaid": "^2.0.17",
105
- "vitest": "^3.2.4"
109
+ "vitest": "^4.1.7"
106
110
  },
107
111
  "tsup": {
108
112
  "entry": [
package/src/bin/server.ts CHANGED
@@ -24,16 +24,28 @@ const server = new CoolorsMcp({
24
24
  instructions:
25
25
  "Advanced color operations server with Material Design 3 support, CSS theme matching, image extraction, and accessibility compliance. Uses HCT color space for perceptually accurate operations.",
26
26
  name: "coolors-mcp",
27
- version: "1.0.0",
27
+ version: "1.1.0",
28
28
  });
29
29
 
30
30
  // Core color operations: conversion, distance metrics, accessibility
31
31
  server.addTool(tools.colorConversionTool);
32
32
  server.addTool(tools.colorDistanceTool);
33
33
  server.addTool(tools.contrastCheckerTool);
34
+ server.addTool(tools.adjustColorTool);
34
35
  server.addTool(tools.paletteGeneratorTool);
35
36
  server.addTool(tools.paletteWithLocksTool);
36
37
  server.addTool(tools.gradientGeneratorTool);
38
+ server.addTool(tools.exportPaletteTool);
39
+
40
+ // Color-blindness simulation & accessibility audit
41
+ server.addTool(tools.simulateColorBlindnessTool);
42
+ server.addTool(tools.checkPaletteAccessibilityTool);
43
+
44
+ // Visual cohesion: tonal scales, state colors, palette consistency, semantics
45
+ server.addTool(tools.generateTonalScaleTool);
46
+ server.addTool(tools.generateStateColorsTool);
47
+ server.addTool(tools.analyzePaletteConsistencyTool);
48
+ server.addTool(tools.generateSemanticPaletteTool);
37
49
 
38
50
  // Material Design 3: theme generation, harmonization, tonal palettes
39
51
  server.addTool(tools.generateMaterialThemeTool);
@@ -27,9 +27,8 @@ describe("Color Extraction", () => {
27
27
  });
28
28
 
29
29
  it("should extract colors with default options", async () => {
30
- const { QuantizerCelebi } = await import(
31
- "../quantize/quantizer_celebi.js"
32
- );
30
+ const { QuantizerCelebi } =
31
+ await import("../quantize/quantizer_celebi.js");
33
32
  const { Score } = await import("../score/score.js");
34
33
 
35
34
  // Mock quantizer to return test colors
@@ -106,9 +105,8 @@ describe("Color Extraction", () => {
106
105
  });
107
106
 
108
107
  it("should extract colors without scoring", async () => {
109
- const { QuantizerCelebi } = await import(
110
- "../quantize/quantizer_celebi.js"
111
- );
108
+ const { QuantizerCelebi } =
109
+ await import("../quantize/quantizer_celebi.js");
112
110
  const { Score } = await import("../score/score.js");
113
111
 
114
112
  const mockQuantized = new Map([
@@ -140,9 +138,8 @@ describe("Color Extraction", () => {
140
138
  });
141
139
 
142
140
  it("should handle empty image data", async () => {
143
- const { QuantizerCelebi } = await import(
144
- "../quantize/quantizer_celebi.js"
145
- );
141
+ const { QuantizerCelebi } =
142
+ await import("../quantize/quantizer_celebi.js");
146
143
  const { Score } = await import("../score/score.js");
147
144
 
148
145
  vi.mocked(QuantizerCelebi.quantize).mockReturnValue(new Map());
@@ -159,9 +156,8 @@ describe("Color Extraction", () => {
159
156
  });
160
157
 
161
158
  it("should calculate correct percentages", async () => {
162
- const { QuantizerCelebi } = await import(
163
- "../quantize/quantizer_celebi.js"
164
- );
159
+ const { QuantizerCelebi } =
160
+ await import("../quantize/quantizer_celebi.js");
165
161
  const { Score } = await import("../score/score.js");
166
162
 
167
163
  const mockQuantized = new Map([
@@ -194,9 +190,8 @@ describe("Color Extraction", () => {
194
190
  });
195
191
 
196
192
  it("should extract theme palette with primary color", async () => {
197
- const { QuantizerCelebi } = await import(
198
- "../quantize/quantizer_celebi.js"
199
- );
193
+ const { QuantizerCelebi } =
194
+ await import("../quantize/quantizer_celebi.js");
200
195
  const { Score } = await import("../score/score.js");
201
196
 
202
197
  const mockQuantized = new Map([
@@ -219,9 +214,8 @@ describe("Color Extraction", () => {
219
214
  });
220
215
 
221
216
  it("should extract secondary with different hue", async () => {
222
- const { QuantizerCelebi } = await import(
223
- "../quantize/quantizer_celebi.js"
224
- );
217
+ const { QuantizerCelebi } =
218
+ await import("../quantize/quantizer_celebi.js");
225
219
  const { Score } = await import("../score/score.js");
226
220
 
227
221
  const mockQuantized = new Map([
@@ -251,9 +245,8 @@ describe("Color Extraction", () => {
251
245
  });
252
246
 
253
247
  it("should find neutral color with low chroma", async () => {
254
- const { QuantizerCelebi } = await import(
255
- "../quantize/quantizer_celebi.js"
256
- );
248
+ const { QuantizerCelebi } =
249
+ await import("../quantize/quantizer_celebi.js");
257
250
  const { Score } = await import("../score/score.js");
258
251
 
259
252
  const mockQuantized = new Map([
@@ -280,9 +273,8 @@ describe("Color Extraction", () => {
280
273
  });
281
274
 
282
275
  it("should find error color in red hue range", async () => {
283
- const { QuantizerCelebi } = await import(
284
- "../quantize/quantizer_celebi.js"
285
- );
276
+ const { QuantizerCelebi } =
277
+ await import("../quantize/quantizer_celebi.js");
286
278
  const { Score } = await import("../score/score.js");
287
279
 
288
280
  const primaryArgb = utils.argbFromRgb(103, 80, 164);
@@ -311,9 +303,8 @@ describe("Color Extraction", () => {
311
303
  });
312
304
 
313
305
  it("should throw error for empty image", async () => {
314
- const { QuantizerCelebi } = await import(
315
- "../quantize/quantizer_celebi.js"
316
- );
306
+ const { QuantizerCelebi } =
307
+ await import("../quantize/quantizer_celebi.js");
317
308
  const { Score } = await import("../score/score.js");
318
309
 
319
310
  vi.mocked(QuantizerCelebi.quantize).mockReturnValue(new Map());
@@ -331,9 +322,8 @@ describe("Color Extraction", () => {
331
322
  });
332
323
 
333
324
  it("should handle single color image", async () => {
334
- const { QuantizerCelebi } = await import(
335
- "../quantize/quantizer_celebi.js"
336
- );
325
+ const { QuantizerCelebi } =
326
+ await import("../quantize/quantizer_celebi.js");
337
327
  const { Score } = await import("../score/score.js");
338
328
 
339
329
  const mockQuantized = new Map([
@@ -0,0 +1,105 @@
1
+ /**
2
+ * APCA — Accessible Perceptual Contrast Algorithm (WCAG 3 draft)
3
+ *
4
+ * Implementation of APCA W3 0.1.9 / SAPC by Andrew Somers (Myndex).
5
+ * Returns Lc, an absolute contrast value typically in the range [-108, +106].
6
+ * Sign indicates polarity: positive when text is darker than background
7
+ * ("dark on light"), negative for light text on dark background. The magnitude
8
+ * (|Lc|) maps to readability thresholds via APCA's "Bronze Simple Tables":
9
+ *
10
+ * |Lc| >= 75 body text
11
+ * |Lc| >= 60 content text
12
+ * |Lc| >= 45 large fluent text
13
+ * |Lc| >= 30 non-content / spot text
14
+ * |Lc| < 15 invisible — fails for any text use
15
+ *
16
+ * Reference: https://github.com/Myndex/SAPC-APCA
17
+ */
18
+
19
+ import type { RGB } from "./types.js";
20
+
21
+ // APCA constants (W3-0.1.9 4g)
22
+ const SA98G = {
23
+ blkClmp: 1.414,
24
+ blkThrs: 0.022,
25
+ deltaYmin: 0.0005,
26
+ loBoTclip: -0.6,
27
+ loBoTexp: 0.74,
28
+ loClip: 0.1,
29
+ // Polarity exponents/factors
30
+ normBG: 0.56,
31
+ normTXT: 0.57,
32
+ revBG: 0.62,
33
+ revTXT: 0.65,
34
+ // Soft-clip and scale
35
+ scaleBoW: 1.14,
36
+ scaleWoB: 1.14,
37
+ trailingW: 0.027,
38
+ } as const;
39
+
40
+ const sRGBtrc = 2.4;
41
+ const Rco = 0.2126729;
42
+ const Gco = 0.7151522;
43
+ const Bco = 0.072175;
44
+
45
+ /**
46
+ * Compute APCA Lc for foreground text on background.
47
+ * `text` is the text/foreground color, `bg` is the background color.
48
+ */
49
+ export function apcaContrast(text: RGB, bg: RGB): number {
50
+ let txtY = apcaY(text);
51
+ let bgY = apcaY(bg);
52
+
53
+ // Soft black clamp
54
+ if (txtY <= SA98G.blkThrs) {
55
+ txtY += Math.pow(SA98G.blkThrs - txtY, SA98G.blkClmp);
56
+ }
57
+ if (bgY <= SA98G.blkThrs) {
58
+ bgY += Math.pow(SA98G.blkThrs - bgY, SA98G.blkClmp);
59
+ }
60
+
61
+ if (Math.abs(bgY - txtY) < SA98G.deltaYmin) return 0;
62
+
63
+ let outputContrast: number;
64
+
65
+ if (bgY > txtY) {
66
+ // Normal polarity (dark text on light background) — positive Lc
67
+ const SAPC =
68
+ (Math.pow(bgY, SA98G.normBG) - Math.pow(txtY, SA98G.normTXT)) *
69
+ SA98G.scaleBoW;
70
+ outputContrast = SAPC < SA98G.loClip ? 0 : SAPC - SA98G.trailingW;
71
+ } else {
72
+ // Reverse polarity (light text on dark background) — negative Lc
73
+ const SAPC =
74
+ (Math.pow(bgY, SA98G.revBG) - Math.pow(txtY, SA98G.revTXT)) *
75
+ SA98G.scaleWoB;
76
+ outputContrast = SAPC > -SA98G.loClip ? 0 : SAPC + SA98G.trailingW;
77
+ }
78
+
79
+ return outputContrast * 100;
80
+ }
81
+
82
+ /**
83
+ * Classify an APCA Lc value against the Bronze Simple level thresholds.
84
+ */
85
+ export function apcaLevel(lc: number): {
86
+ body: boolean;
87
+ content: boolean;
88
+ large: boolean;
89
+ spot: boolean;
90
+ } {
91
+ const abs = Math.abs(lc);
92
+ return {
93
+ body: abs >= 75,
94
+ content: abs >= 60,
95
+ large: abs >= 45,
96
+ spot: abs >= 30,
97
+ };
98
+ }
99
+
100
+ function apcaY(rgb: RGB): number {
101
+ const r = Math.pow(rgb.r / 255, sRGBtrc);
102
+ const g = Math.pow(rgb.g / 255, sRGBtrc);
103
+ const b = Math.pow(rgb.b / 255, sRGBtrc);
104
+ return Rco * r + Gco * g + Bco * b;
105
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Color Vision Deficiency (CVD) simulation.
3
+ *
4
+ * Linear-sRGB transformation matrices from Machado, Oliveira, and Fernandes
5
+ * (2009), "A Physiologically-based Model for Simulation of Color Vision
6
+ * Deficiency". Severity 1.0 matrices are used for the dichromatic forms
7
+ * (protanopia/deuteranopia/tritanopia); severity 0.6 matrices are used for the
8
+ * milder anomaly forms. Achromatopsia uses ITU-R BT.709 luminance.
9
+ *
10
+ * Pipeline: sRGB → linear sRGB → matrix → sRGB.
11
+ */
12
+
13
+ import type { RGB } from "./types.js";
14
+
15
+ export type CvdType =
16
+ | "achromatopsia"
17
+ | "deuteranomaly"
18
+ | "deuteranopia"
19
+ | "protanomaly"
20
+ | "protanopia"
21
+ | "tritanomaly"
22
+ | "tritanopia";
23
+
24
+ // Confusion-line matrices applied in linear sRGB space (Machado et al. 2009
25
+ // linear approximation, common in libraries like color-blind / colorjs.io).
26
+ // Each matrix maps linear-sRGB to the visible linear-sRGB for the given CVD.
27
+ const CVD_MATRICES: Record<
28
+ Exclude<CvdType, "achromatopsia">,
29
+ [number, number, number, number, number, number, number, number, number]
30
+ > = {
31
+ deuteranomaly: [
32
+ 0.547494, 0.607765, -0.155259, 0.181692, 0.781742, 0.036566, -0.01041,
33
+ 0.027275, 0.983136,
34
+ ],
35
+ deuteranopia: [
36
+ 0.367322, 0.860646, -0.227968, 0.280085, 0.672501, 0.047413, -0.01182,
37
+ 0.04294, 0.968881,
38
+ ],
39
+ // Mild forms (-omaly: severity ~0.6, Machado severity 0.6 table)
40
+ protanomaly: [
41
+ 0.458064, 0.679578, -0.137642, 0.092785, 0.846313, 0.060902, -0.007494,
42
+ -0.016807, 1.024301,
43
+ ],
44
+ // Strong forms (-opia: full dichromacy, severity 1.0)
45
+ protanopia: [
46
+ 0.152286, 1.052583, -0.204868, 0.114503, 0.786281, 0.099216, -0.003882,
47
+ -0.048116, 1.051998,
48
+ ],
49
+ tritanomaly: [
50
+ 1.193214, -0.109812, -0.083402, 0.058694, 0.901185, 0.040121, -0.005978,
51
+ 0.401901, 0.604077,
52
+ ],
53
+ tritanopia: [
54
+ 1.255528, -0.076749, -0.178779, -0.078411, 0.930809, 0.147602, 0.004733,
55
+ 0.691367, 0.3039,
56
+ ],
57
+ };
58
+
59
+ /**
60
+ * Simulate how a color appears to a viewer with the given color vision
61
+ * deficiency.
62
+ */
63
+ export function simulateCvd(rgb: RGB, type: CvdType): RGB {
64
+ if (type === "achromatopsia") {
65
+ // ITU-R BT.709 luminance (Rec. 709) — perceived brightness.
66
+ const y = 0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b;
67
+ const g = Math.max(0, Math.min(255, Math.round(y)));
68
+ return { b: g, g, r: g };
69
+ }
70
+
71
+ const m = CVD_MATRICES[type];
72
+ const r = srgbToLinear(rgb.r);
73
+ const g = srgbToLinear(rgb.g);
74
+ const b = srgbToLinear(rgb.b);
75
+
76
+ const rOut = m[0] * r + m[1] * g + m[2] * b;
77
+ const gOut = m[3] * r + m[4] * g + m[5] * b;
78
+ const bOut = m[6] * r + m[7] * g + m[8] * b;
79
+
80
+ return {
81
+ b: linearToSrgb(bOut),
82
+ g: linearToSrgb(gOut),
83
+ r: linearToSrgb(rOut),
84
+ };
85
+ }
86
+
87
+ function linearToSrgb(c: number): number {
88
+ const v = c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
89
+ return Math.max(0, Math.min(255, Math.round(v * 255)));
90
+ }
91
+
92
+ function srgbToLinear(c: number): number {
93
+ const n = c / 255;
94
+ return n <= 0.04045 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4);
95
+ }
96
+
97
+ /**
98
+ * Approximate share of the population affected by each CVD type (worldwide,
99
+ * combining male+female prevalence as reported by Colour Blind Awareness).
100
+ */
101
+ export const CVD_PREVALENCE: Record<CvdType, number> = {
102
+ achromatopsia: 0.003,
103
+ deuteranomaly: 5.0,
104
+ deuteranopia: 1.0,
105
+ protanomaly: 1.0,
106
+ protanopia: 1.0,
107
+ tritanomaly: 0.01,
108
+ tritanopia: 0.003,
109
+ };
@@ -1,5 +1,5 @@
1
1
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
- import { EventEmitter } from "events";
2
+ import { EventEmitter } from "node:events";
3
3
  import { StrictEventEmitter } from "strict-event-emitter-types";
4
4
 
5
5
  import { CoolorsMCPSession } from "./session.js";