@trishchuk/coolors-mcp 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +20 -8
- package/.github/ISSUE_TEMPLATE/feature_request.md +22 -8
- package/.github/pull_request_template.md +33 -8
- package/.github/workflows/ci.yml +107 -104
- package/.github/workflows/deploy-docs.yml +14 -11
- package/.github/workflows/release.yml +25 -23
- package/README.md +149 -15
- package/dist/bin/server.js +997 -256
- package/dist/bin/server.js.map +1 -1
- package/dist/{chunk-P3ARRKLS.js → chunk-HOMDMKUY.js} +3 -1
- package/dist/{chunk-P3ARRKLS.js.map → chunk-HOMDMKUY.js.map} +1 -1
- package/dist/{chunk-IQ7NN26V.js → chunk-LHW2ZTOU.js} +14 -2
- package/dist/chunk-LHW2ZTOU.js.map +1 -0
- package/dist/color/index.js +1 -1
- package/dist/coolors-mcp.d.ts +4 -4
- package/dist/coolors-mcp.js +1 -1
- package/docs/.vitepress/components/ClientGrid.vue +9 -3
- package/docs/.vitepress/components/CodeBlock.vue +51 -44
- package/docs/.vitepress/components/ConfigModal.vue +151 -67
- package/docs/.vitepress/components/DiagramModal.vue +186 -154
- package/docs/.vitepress/components/TroubleshootingModal.vue +101 -96
- package/docs/.vitepress/config.js +171 -141
- package/docs/.vitepress/theme/FundingLayout.vue +65 -54
- package/docs/.vitepress/theme/Layout.vue +21 -21
- package/docs/.vitepress/theme/components/AdBanner.vue +73 -52
- package/docs/.vitepress/theme/components/AdPlaceholder.vue +3 -3
- package/docs/.vitepress/theme/components/FundingEffects.vue +77 -53
- package/docs/.vitepress/theme/components/FundingHero.vue +78 -63
- package/docs/.vitepress/theme/components/SupportSection.vue +106 -89
- package/docs/.vitepress/theme/custom-app.css +19 -12
- package/docs/.vitepress/theme/custom.css +33 -25
- package/docs/.vitepress/theme/index.js +19 -16
- package/docs/concepts/accessibility.md +59 -47
- package/docs/concepts/color-spaces.md +28 -6
- package/docs/concepts/distance-metrics.md +45 -30
- package/docs/concepts/hct.md +30 -27
- package/docs/concepts/image-analysis.md +52 -21
- package/docs/concepts/material-design.md +43 -17
- package/docs/concepts/theme-matching.md +64 -40
- package/docs/examples/basic-colors.md +92 -108
- package/docs/examples/creating-themes.md +104 -108
- package/docs/examples/css-refactoring.md +33 -29
- package/docs/examples/image-extraction.md +145 -138
- package/docs/getting-started.md +45 -34
- package/docs/index.md +5 -1
- package/docs/installation.md +15 -1
- package/docs/tools/accessibility.md +74 -68
- package/docs/tools/image-extraction.md +62 -54
- package/docs/tools/theme-matching.md +45 -42
- package/eslint.config.ts +13 -0
- package/jsr.json +1 -1
- package/package.json +17 -13
- package/src/bin/server.ts +13 -1
- package/src/color/__tests__/extract-colors.test.ts +20 -30
- package/src/color/apca.ts +105 -0
- package/src/color/color-blindness.ts +109 -0
- package/src/coolors-mcp.ts +1 -1
- package/src/session.ts +10 -2
- package/src/theme/matcher.ts +1 -1
- package/src/theme/refactor.ts +1 -1
- package/src/theme/types.ts +3 -0
- package/src/tools/__tests__/cohesion.test.ts +97 -0
- package/src/tools/__tests__/color-blindness.test.ts +45 -0
- package/src/tools/__tests__/color-conversion.test.ts +38 -0
- package/src/tools/__tests__/contrast-checker.test.ts +56 -0
- package/src/tools/__tests__/palette-export.test.ts +54 -0
- package/src/tools/adjust-color.tool.ts +80 -0
- package/src/tools/cohesion.tools.ts +380 -0
- package/src/tools/color-blindness.tool.ts +168 -0
- package/src/tools/color-conversion.tool.ts +1 -1
- package/src/tools/contrast-checker.tool.ts +53 -14
- package/src/tools/dislike-analyzer.tool.ts +41 -54
- package/src/tools/image-extraction.tools.ts +62 -115
- package/src/tools/index.ts +15 -2
- package/src/tools/palette-export.tool.ts +174 -0
- package/src/tools/palette-with-locks.tool.ts +8 -6
- package/src/types.ts +2 -3
- package/tsconfig.json +12 -2
- package/vitest.config.js +1 -3
- package/.claude/settings.local.json +0 -39
- package/.env +0 -2
- package/.mcp.json +0 -12
- package/CLAUDE.md +0 -201
- package/DOCUMENTATION.md +0 -274
- package/GEMINI.md +0 -54
- package/demo/content_based_color.png +0 -0
- package/demo/music-player.html +0 -621
- package/demo/podcast-player.html +0 -903
- package/dist/chunk-IQ7NN26V.js.map +0 -1
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js +0 -93
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js.map +0 -7
- package/docs/.vitepress/cache/deps/_metadata.json +0 -127
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js +0 -9
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +0 -7
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +0 -12683
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js.map +0 -7
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +0 -9719
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js.map +0 -7
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +0 -4710
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +0 -7
- package/docs/.vitepress/cache/deps/cytoscape.js +0 -30278
- package/docs/.vitepress/cache/deps/cytoscape.js.map +0 -7
- package/docs/.vitepress/cache/deps/dayjs.js +0 -285
- package/docs/.vitepress/cache/deps/dayjs.js.map +0 -7
- package/docs/.vitepress/cache/deps/debug.js +0 -468
- package/docs/.vitepress/cache/deps/debug.js.map +0 -7
- package/docs/.vitepress/cache/deps/package.json +0 -3
- package/docs/.vitepress/cache/deps/prismjs.js +0 -1466
- package/docs/.vitepress/cache/deps/prismjs.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +0 -228
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +0 -142
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js +0 -27
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js +0 -65
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +0 -53
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js.map +0 -7
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +0 -73
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4507
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -584
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +0 -1146
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +0 -1667
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +0 -7
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js +0 -1814
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js.map +0 -7
- package/docs/.vitepress/cache/deps/vue.js +0 -344
- package/docs/.vitepress/cache/deps/vue.js.map +0 -7
- package/examples/theme-matching.md +0 -113
- package/mcp-config.json +0 -8
- package/note.md +0 -35
- package/research_results.md +0 -53
- package/src/tools/colors.ts +0 -31
- package/src/tools/registry.ts +0 -142
- package/src/tools/simple-tools.ts +0 -37
|
@@ -8,24 +8,24 @@ Find the closest matching theme variable for a given color.
|
|
|
8
8
|
|
|
9
9
|
### Parameters
|
|
10
10
|
|
|
11
|
-
| Parameter
|
|
12
|
-
|
|
13
|
-
| `color`
|
|
14
|
-
| `themeCSS`
|
|
15
|
-
| `context`
|
|
16
|
-
| `minConfidence` | number | ❌
|
|
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;
|
|
23
|
-
confidence: number;
|
|
22
|
+
match: string; // CSS variable name
|
|
23
|
+
confidence: number; // Confidence score (0-100)
|
|
24
24
|
originalColor: string; // Input color
|
|
25
|
-
themeColor: string;
|
|
26
|
-
distance: number;
|
|
25
|
+
themeColor: string; // Matched theme color
|
|
26
|
+
distance: number; // Perceptual distance
|
|
27
27
|
semantic: {
|
|
28
|
-
role: string;
|
|
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
|
|
121
|
-
|
|
122
|
-
| `css`
|
|
123
|
-
| `themeCSS`
|
|
124
|
-
| `minConfidence`
|
|
125
|
-
| `preserveOriginal` | boolean | ❌
|
|
126
|
-
| `generateReport`
|
|
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
|
|
240
|
-
|
|
241
|
-
| `colors`
|
|
242
|
-
| `themeCSS` | string
|
|
243
|
-
| `context`
|
|
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;
|
|
250
|
+
color: string; // Input color
|
|
251
251
|
match: string | null; // Matched variable or null
|
|
252
|
-
confidence: number;
|
|
253
|
-
distance: number;
|
|
252
|
+
confidence: number; // Match confidence
|
|
253
|
+
distance: number; // Perceptual distance
|
|
254
254
|
}>;
|
|
255
255
|
summary: {
|
|
256
|
-
total: number;
|
|
257
|
-
matched: number;
|
|
258
|
-
unmatched: number;
|
|
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
|
|
339
|
-
|
|
340
|
-
| `sourceColor`
|
|
341
|
-
| `prefix`
|
|
342
|
-
| `includeTones` | number[] | ❌
|
|
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
|
|
444
|
-
|
|
445
|
-
| Production
|
|
446
|
-
| Development | 70-85%
|
|
447
|
-
| Exploration | 50-70%
|
|
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(
|
|
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(
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trishchuk/coolors-mcp",
|
|
3
|
-
"version": "1.
|
|
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": "^
|
|
54
|
+
"mcp-proxy": "^6.5.1",
|
|
50
55
|
"strict-event-emitter-types": "^2.0.0",
|
|
51
|
-
"undici": "^
|
|
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": "^
|
|
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": "^
|
|
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": "^
|
|
91
|
+
"@wong2/mcp-cli": "^2.0.0",
|
|
88
92
|
"arktype": "^2.1.20",
|
|
89
|
-
"eslint": "^
|
|
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": "^
|
|
102
|
+
"semantic-release": "^25.0.3",
|
|
99
103
|
"tsup": "^8.5.0",
|
|
100
|
-
"typescript": "^
|
|
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": "^
|
|
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.
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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 } =
|
|
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
|
+
};
|
package/src/coolors-mcp.ts
CHANGED
|
@@ -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";
|