@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,142 @@
1
+ import { Prompt, Tool } from "@modelcontextprotocol/sdk/types.js"; // Each tool definition includes its metadata, schema, prompt, and execution logic in one place.
2
+ import { ZodError, ZodTypeAny } from "zod";
3
+ import { zodToJsonSchema } from "zod-to-json-schema";
4
+
5
+ export interface UnifiedTool {
6
+ category?: "codex" | "simple" | "utility";
7
+ description: string;
8
+ execute: (
9
+ args: Record<string, unknown>,
10
+ onProgress?: (newOutput: string) => void,
11
+ ) => Promise<string>;
12
+
13
+ name: string;
14
+
15
+ prompt?: {
16
+ arguments?: Array<{
17
+ description: string;
18
+ name: string;
19
+ required: boolean;
20
+ }>;
21
+ description: string;
22
+ };
23
+ zodSchema: ZodTypeAny;
24
+ }
25
+
26
+ export const toolRegistry: UnifiedTool[] = [];
27
+
28
+ export async function executeTool(
29
+ toolName: string,
30
+ args: Record<string, unknown>,
31
+ onProgress?: (newOutput: string) => void,
32
+ ): Promise<string> {
33
+ const tool = toolRegistry.find((t) => t.name === toolName);
34
+ if (!tool) {
35
+ throw new Error(`Unknown tool: ${toolName}`);
36
+ }
37
+ try {
38
+ const validatedArgs = tool.zodSchema.parse(args);
39
+ return tool.execute(validatedArgs, onProgress);
40
+ } catch (error) {
41
+ if (error instanceof ZodError) {
42
+ const issues = error.issues
43
+ .map((issue) => `${issue.path.join(".")}: ${issue.message}`)
44
+ .join(", ");
45
+ throw new Error(`Invalid arguments for ${toolName}: ${issues}`);
46
+ }
47
+ throw error;
48
+ }
49
+ }
50
+
51
+ export function getPromptDefinitions(): Prompt[] {
52
+ // Helper to get MCP Prompt definitions from registry
53
+ return toolRegistry
54
+ .filter((tool) => tool.prompt)
55
+ .map((tool) => ({
56
+ arguments:
57
+ tool.prompt!.arguments || extractPromptArguments(tool.zodSchema),
58
+ description: tool.prompt!.description,
59
+ name: tool.name,
60
+ }));
61
+ }
62
+
63
+ export function getPromptMessage(
64
+ toolName: string,
65
+ args: Record<string, unknown>,
66
+ ): string {
67
+ const tool = toolRegistry.find((t) => t.name === toolName);
68
+ if (!tool?.prompt) {
69
+ throw new Error(`No prompt defined for tool: ${toolName}`);
70
+ }
71
+ const paramStrings: string[] = [];
72
+
73
+ if (args.prompt) {
74
+ paramStrings.push(args.prompt as string);
75
+ }
76
+
77
+ Object.entries(args).forEach(([key, value]) => {
78
+ if (
79
+ key !== "prompt" &&
80
+ value !== undefined &&
81
+ value !== null &&
82
+ value !== false
83
+ ) {
84
+ if (typeof value === "boolean" && value) {
85
+ paramStrings.push(`[${key}]`);
86
+ } else if (typeof value !== "boolean") {
87
+ paramStrings.push(`(${key}: ${value})`);
88
+ }
89
+ }
90
+ });
91
+
92
+ return `Use the ${toolName} tool${paramStrings.length > 0 ? ": " + paramStrings.join(" ") : ""}`;
93
+ }
94
+
95
+ export function getToolDefinitions(): Tool[] {
96
+ // get Tool definitions from registry
97
+ return toolRegistry.map((tool) => {
98
+ const raw = zodToJsonSchema(tool.zodSchema, tool.name) as {
99
+ definitions?: Record<string, unknown>;
100
+ properties?: Record<string, unknown>;
101
+ required?: string[];
102
+ };
103
+ const def = (raw.definitions?.[tool.name] ?? raw) as {
104
+ properties?: Record<string, unknown>;
105
+ required?: string[];
106
+ };
107
+ const inputSchema: Tool["inputSchema"] = {
108
+ properties: def.properties || {},
109
+ required: def.required || [],
110
+ type: "object",
111
+ };
112
+
113
+ return {
114
+ description: tool.description,
115
+ inputSchema,
116
+ name: tool.name,
117
+ };
118
+ });
119
+ }
120
+
121
+ export function toolExists(toolName: string): boolean {
122
+ return toolRegistry.some((t) => t.name === toolName);
123
+ }
124
+
125
+ function extractPromptArguments(zodSchema: ZodTypeAny): Array<{
126
+ description: string;
127
+ name: string;
128
+ required: boolean;
129
+ }> {
130
+ const jsonSchema = zodToJsonSchema(zodSchema) as {
131
+ properties?: Record<string, { description?: string }>;
132
+ required?: string[];
133
+ };
134
+ const properties = jsonSchema.properties || {};
135
+ const required = jsonSchema.required || [];
136
+
137
+ return Object.entries(properties).map(([name, prop]) => ({
138
+ description: prop.description || `${name} parameter`,
139
+ name,
140
+ required: required.includes(name),
141
+ }));
142
+ }
@@ -0,0 +1,37 @@
1
+ import { z } from "zod";
2
+
3
+ import { UnifiedTool } from "./registry.js";
4
+
5
+ const pingArgsSchema = z.object({
6
+ prompt: z.string().default("").describe("Message to echo "),
7
+ });
8
+
9
+ export const pingTool: UnifiedTool = {
10
+ category: "simple",
11
+ description: "Echo",
12
+ execute: async (args) => {
13
+ const message = args.prompt || args.message || "Pong!";
14
+ // Return message directly to avoid cross-platform issues with echo command
15
+ return message as string;
16
+ },
17
+ name: "ping",
18
+ prompt: {
19
+ description: "Echo test message with structured response.",
20
+ },
21
+ zodSchema: pingArgsSchema,
22
+ };
23
+
24
+ const helpArgsSchema = z.object({});
25
+
26
+ export const helpTool: UnifiedTool = {
27
+ category: "simple",
28
+ description: "receive help information",
29
+ execute: async () => {
30
+ return "Help information for Coolors MCP:\n\nAvailable tools for color manipulation, theme generation, and CSS refactoring.\nSee documentation for details.";
31
+ },
32
+ name: "Help",
33
+ prompt: {
34
+ description: "receive help information",
35
+ },
36
+ zodSchema: helpArgsSchema,
37
+ };
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Theme Matching Tools
3
+ * Tools for matching colors to theme variables and refactoring CSS
4
+ */
5
+
6
+ import { z } from "zod";
7
+
8
+ import { corePaletteFromRgb, parseColor, rgbToHex } from "../color/index.js";
9
+ import {
10
+ type ColorContext,
11
+ findBatchMatches,
12
+ findClosestThemeColor,
13
+ generateRefactoringReport,
14
+ parseThemeVariables,
15
+ refactorCss,
16
+ } from "../theme/index.js";
17
+
18
+ /**
19
+ * Match Theme Color Tool
20
+ */
21
+ export const matchThemeColorTool = {
22
+ description: "Find the closest matching theme variable for a given color",
23
+ execute: async (args: {
24
+ color: string;
25
+ context?: ColorContext;
26
+ minConfidence?: number;
27
+ themeCSS: string;
28
+ }) => {
29
+ // Parse theme variables from CSS
30
+ const themeVariables = parseThemeVariables(args.themeCSS);
31
+
32
+ // Find closest match
33
+ const match = findClosestThemeColor(args.color, themeVariables, {
34
+ contextType: args.context,
35
+ });
36
+
37
+ if (!match) {
38
+ return "No matching theme variable found";
39
+ }
40
+
41
+ const minConfidence = args.minConfidence ?? 70;
42
+ if (match.confidence < minConfidence) {
43
+ return `Best match below confidence threshold:
44
+ Variable: ${match.variable}
45
+ Distance: ${match.distance.toFixed(2)}
46
+ Confidence: ${match.confidence}%
47
+
48
+ Consider adding a new theme variable for this color.`;
49
+ }
50
+
51
+ let result = `Best Match:
52
+ Variable: ${match.variable}
53
+ Value: ${match.value}
54
+ Distance: ${match.distance.toFixed(2)}
55
+ Confidence: ${match.confidence}%`;
56
+
57
+ if (match.semanticRole) {
58
+ result += `\nSemantic Role: ${match.semanticRole}`;
59
+ }
60
+
61
+ if (match.accessibilityInfo) {
62
+ result += `\n\nAccessibility:
63
+ Contrast with background: ${match.accessibilityInfo.contrastWithBackground.toFixed(2)}
64
+ Contrast with foreground: ${match.accessibilityInfo.contrastWithForeground.toFixed(2)}
65
+ Meets AA: ${match.accessibilityInfo.meetsAA ? "Yes" : "No"}
66
+ Meets AAA: ${match.accessibilityInfo.meetsAAA ? "Yes" : "No"}`;
67
+ }
68
+
69
+ if (match.alternatives.length > 0) {
70
+ result += `\n\nAlternatives:`;
71
+ for (const alt of match.alternatives) {
72
+ result += `\n- ${alt.variable} (confidence: ${alt.confidence}%)`;
73
+ }
74
+ }
75
+
76
+ return result;
77
+ },
78
+ name: "match_theme_color",
79
+ parameters: z.object({
80
+ color: z.string().describe("Color to match (hex, rgb, hsl)"),
81
+ context: z
82
+ .enum(["text", "background", "border", "shadow", "accent", "decorative"])
83
+ .optional()
84
+ .describe("Usage context for better matching"),
85
+ minConfidence: z
86
+ .number()
87
+ .min(0)
88
+ .max(100)
89
+ .optional()
90
+ .default(70)
91
+ .describe("Minimum confidence threshold (0-100)"),
92
+ themeCSS: z.string().describe("CSS containing theme variables"),
93
+ }),
94
+ };
95
+
96
+ /**
97
+ * Refactor CSS with Theme Tool
98
+ */
99
+ export const refactorCssWithThemeTool = {
100
+ description:
101
+ "Refactor CSS to use theme variables instead of hardcoded colors",
102
+ execute: async (args: {
103
+ css: string;
104
+ generateReport?: boolean;
105
+ minConfidence?: number;
106
+ preserveOriginal?: boolean;
107
+ themeCSS: string;
108
+ }) => {
109
+ // Parse theme variables
110
+ const themeVariables = parseThemeVariables(args.themeCSS);
111
+
112
+ // Refactor CSS
113
+ const result = refactorCss(args.css, themeVariables, {
114
+ addComments: args.preserveOriginal ?? true,
115
+ minConfidence: args.minConfidence ?? 70,
116
+ preserveOriginal: args.preserveOriginal ?? true,
117
+ });
118
+
119
+ let output = `Refactoring Complete!
120
+
121
+ Statistics:
122
+ - Total colors found: ${result.statistics.totalColors}
123
+ - Colors replaced: ${result.statistics.replacedColors}
124
+ - Average confidence: ${result.statistics.averageConfidence}%
125
+ - Accessibility issues: ${result.statistics.accessibilityIssues}
126
+
127
+ Refactored CSS:
128
+ ----------------------------------------
129
+ ${result.refactored}
130
+ ----------------------------------------`;
131
+
132
+ if (result.warnings.length > 0) {
133
+ output += `\n\nWarnings (${result.warnings.length}):`;
134
+ for (const warning of result.warnings.slice(0, 5)) {
135
+ output += `\n- ${warning.message}`;
136
+ if (warning.suggestion) {
137
+ output += `\n Suggestion: ${warning.suggestion}`;
138
+ }
139
+ }
140
+ if (result.warnings.length > 5) {
141
+ output += `\n... and ${result.warnings.length - 5} more warnings`;
142
+ }
143
+ }
144
+
145
+ if (args.generateReport) {
146
+ output += `\n\n${generateRefactoringReport(result)}`;
147
+ }
148
+
149
+ return output;
150
+ },
151
+ name: "refactor_css_with_theme",
152
+ parameters: z.object({
153
+ css: z.string().describe("CSS content to refactor"),
154
+ generateReport: z
155
+ .boolean()
156
+ .optional()
157
+ .default(false)
158
+ .describe("Generate detailed refactoring report"),
159
+ minConfidence: z
160
+ .number()
161
+ .min(0)
162
+ .max(100)
163
+ .optional()
164
+ .default(70)
165
+ .describe("Minimum confidence for replacements"),
166
+ preserveOriginal: z
167
+ .boolean()
168
+ .optional()
169
+ .default(true)
170
+ .describe("Keep original values as comments"),
171
+ themeCSS: z.string().describe("CSS containing theme variables"),
172
+ }),
173
+ };
174
+
175
+ /**
176
+ * Match Theme Colors Batch Tool
177
+ */
178
+ export const matchThemeColorsBatchTool = {
179
+ description: "Find theme matches for multiple colors at once",
180
+ execute: async (args: {
181
+ colors: string[];
182
+ context?: ColorContext;
183
+ themeCSS: string;
184
+ }) => {
185
+ // Parse theme variables
186
+ const themeVariables = parseThemeVariables(args.themeCSS);
187
+
188
+ // Find matches for all colors
189
+ const matches = findBatchMatches(args.colors, themeVariables, {
190
+ contextType: args.context,
191
+ });
192
+
193
+ let result = `Theme Color Matches:\n`;
194
+ let matchCount = 0;
195
+ let totalConfidence = 0;
196
+
197
+ for (const [color, match] of matches.entries()) {
198
+ if (match) {
199
+ matchCount++;
200
+ totalConfidence += match.confidence;
201
+ result += `\n${color} → ${match.variable} (${match.confidence}%)`;
202
+ } else {
203
+ result += `\n${color} → No match found`;
204
+ }
205
+ }
206
+
207
+ result += `\n\nSummary:
208
+ - Matched: ${matchCount}/${args.colors.length}
209
+ - Average confidence: ${matchCount > 0 ? Math.round(totalConfidence / matchCount) : 0}%`;
210
+
211
+ return result;
212
+ },
213
+ name: "match_theme_colors_batch",
214
+ parameters: z.object({
215
+ colors: z
216
+ .array(z.string())
217
+ .min(1)
218
+ .max(50)
219
+ .describe("Array of colors to match"),
220
+ context: z
221
+ .enum(["text", "background", "border", "shadow", "accent", "decorative"])
222
+ .optional()
223
+ .describe("Usage context for all colors"),
224
+ themeCSS: z.string().describe("CSS containing theme variables"),
225
+ }),
226
+ };
227
+
228
+ /**
229
+ * Generate Theme CSS Tool
230
+ */
231
+ export const generateThemeCssTool = {
232
+ description:
233
+ "Generate CSS custom properties for a complete theme from a source color",
234
+ execute: async (args: {
235
+ includeTones?: number[];
236
+ prefix?: string;
237
+ sourceColor: string;
238
+ }) => {
239
+ const source = parseColor(args.sourceColor);
240
+ if (!source) {
241
+ return `Invalid color format: ${args.sourceColor}`;
242
+ }
243
+
244
+ const corePalette = corePaletteFromRgb(source);
245
+ const tones = args.includeTones || [
246
+ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 100,
247
+ ];
248
+ const prefix = args.prefix || "color";
249
+
250
+ let css = `:root {\n`;
251
+
252
+ // Generate primary palette
253
+ css += ` /* Primary Colors */\n`;
254
+ for (const tone of tones) {
255
+ const color = rgbToHex(corePalette.primary.tone(tone));
256
+ css += ` --${prefix}-primary-${tone}: ${color};\n`;
257
+ }
258
+
259
+ // Generate secondary palette
260
+ css += `\n /* Secondary Colors */\n`;
261
+ for (const tone of tones) {
262
+ const color = rgbToHex(corePalette.secondary.tone(tone));
263
+ css += ` --${prefix}-secondary-${tone}: ${color};\n`;
264
+ }
265
+
266
+ // Generate tertiary palette
267
+ css += `\n /* Tertiary Colors */\n`;
268
+ for (const tone of tones) {
269
+ const color = rgbToHex(corePalette.tertiary.tone(tone));
270
+ css += ` --${prefix}-tertiary-${tone}: ${color};\n`;
271
+ }
272
+
273
+ // Generate error palette
274
+ css += `\n /* Error Colors */\n`;
275
+ for (const tone of tones) {
276
+ const color = rgbToHex(corePalette.error.tone(tone));
277
+ css += ` --${prefix}-error-${tone}: ${color};\n`;
278
+ }
279
+
280
+ // Generate neutral palette
281
+ css += `\n /* Neutral Colors */\n`;
282
+ for (const tone of tones) {
283
+ const color = rgbToHex(corePalette.neutral.tone(tone));
284
+ css += ` --${prefix}-neutral-${tone}: ${color};\n`;
285
+ }
286
+
287
+ // Generate neutral variant palette
288
+ css += `\n /* Neutral Variant Colors */\n`;
289
+ for (const tone of tones) {
290
+ const color = rgbToHex(corePalette.neutralVariant.tone(tone));
291
+ css += ` --${prefix}-neutral-variant-${tone}: ${color};\n`;
292
+ }
293
+
294
+ css += `}\n\n`;
295
+
296
+ // Add semantic mappings
297
+ css += `/* Semantic Color Mappings (Light Theme) */\n`;
298
+ css += `:root {\n`;
299
+ css += ` --${prefix}-primary: var(--${prefix}-primary-40);\n`;
300
+ css += ` --${prefix}-on-primary: var(--${prefix}-primary-100);\n`;
301
+ css += ` --${prefix}-primary-container: var(--${prefix}-primary-90);\n`;
302
+ css += ` --${prefix}-on-primary-container: var(--${prefix}-primary-10);\n`;
303
+ css += ` \n`;
304
+ css += ` --${prefix}-secondary: var(--${prefix}-secondary-40);\n`;
305
+ css += ` --${prefix}-on-secondary: var(--${prefix}-secondary-100);\n`;
306
+ css += ` --${prefix}-secondary-container: var(--${prefix}-secondary-90);\n`;
307
+ css += ` --${prefix}-on-secondary-container: var(--${prefix}-secondary-10);\n`;
308
+ css += ` \n`;
309
+ css += ` --${prefix}-background: var(--${prefix}-neutral-99);\n`;
310
+ css += ` --${prefix}-on-background: var(--${prefix}-neutral-10);\n`;
311
+ css += ` --${prefix}-surface: var(--${prefix}-neutral-99);\n`;
312
+ css += ` --${prefix}-on-surface: var(--${prefix}-neutral-10);\n`;
313
+ css += `}\n`;
314
+
315
+ return css;
316
+ },
317
+ name: "generate_theme_css",
318
+ parameters: z.object({
319
+ includeTones: z
320
+ .array(z.number())
321
+ .optional()
322
+ .describe(
323
+ "Tone values to include (default: 0,10,20,30,40,50,60,70,80,90,95,99,100)",
324
+ ),
325
+ prefix: z
326
+ .string()
327
+ .optional()
328
+ .default("color")
329
+ .describe(
330
+ "Prefix for CSS variables (e.g., 'color' → --color-primary-50)",
331
+ ),
332
+ sourceColor: z.string().describe("Source color for theme generation"),
333
+ }),
334
+ };
package/src/types.ts ADDED
@@ -0,0 +1,182 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { GetPromptResult, Root } from "@modelcontextprotocol/sdk/types.js";
3
+ import { StandardSchemaV1 } from "@standard-schema/spec";
4
+
5
+ export type AudioContent = {
6
+ data: string;
7
+ mimeType: string;
8
+ type: "audio";
9
+ };
10
+
11
+ export type Content =
12
+ | AudioContent
13
+ | ImageContent
14
+ | ResourceContent
15
+ | ResourceLink
16
+ | TextContent;
17
+
18
+ export type ContentResult = {
19
+ content: Content[];
20
+ isError?: boolean;
21
+ };
22
+
23
+ export type Context<T extends CoolorsMCPSessionAuth> = {
24
+ client: {
25
+ version: ReturnType<Server["getClientVersion"]>;
26
+ };
27
+ log: {
28
+ debug: (message: string, data?: SerializableValue) => void;
29
+ error: (message: string, data?: SerializableValue) => void;
30
+ info: (message: string, data?: SerializableValue) => void;
31
+ warn: (message: string, data?: SerializableValue) => void;
32
+ };
33
+ reportProgress: (progress: Progress) => Promise<void>;
34
+ session: T | undefined;
35
+ streamContent: (content: Content | Content[]) => Promise<void>;
36
+ };
37
+
38
+ export type CoolorsMCPEvents = {
39
+ connect: (event: { session: any }) => void;
40
+ disconnect: (event: { session: any }) => void;
41
+ };
42
+
43
+ export type CoolorsMCPSessionAuth = Record<string, unknown> | undefined;
44
+
45
+ export type CoolorsMCPSessionData = Record<string, unknown>;
46
+
47
+ export type CoolorsMCPSessionEvents = {
48
+ error: (event: { error: Error }) => void;
49
+ ready: () => void;
50
+ rootsChanged: (event: { roots: Root[] }) => void;
51
+ };
52
+
53
+ export type ImageContent = {
54
+ data: string;
55
+ mimeType: string;
56
+ type: "image";
57
+ };
58
+
59
+ export type Literal = boolean | null | number | string | undefined;
60
+
61
+ export interface Logger {
62
+ debug(...args: unknown[]): void;
63
+
64
+ error(...args: unknown[]): void;
65
+
66
+ info(...args: unknown[]): void;
67
+
68
+ log(...args: unknown[]): void;
69
+
70
+ warn(...args: unknown[]): void;
71
+ }
72
+
73
+ export type LoggingLevel =
74
+ | "alert"
75
+ | "critical"
76
+ | "debug"
77
+ | "emergency"
78
+ | "error"
79
+ | "info"
80
+ | "notice"
81
+ | "warning";
82
+
83
+ export type Progress = {
84
+ progress: number;
85
+ total?: number;
86
+ };
87
+
88
+ export type PromptResult = Pick<GetPromptResult, "messages"> | string;
89
+
90
+ export type ResourceContent = {
91
+ resource: {
92
+ blob?: string;
93
+ mimeType?: string;
94
+ text?: string;
95
+ uri: string;
96
+ };
97
+ type: "resource";
98
+ };
99
+
100
+ export type ResourceLink = {
101
+ description?: string;
102
+ mimeType?: string;
103
+ name: string;
104
+ title?: string;
105
+ type: "resource_link";
106
+ uri: string;
107
+ };
108
+
109
+ export type SerializableValue =
110
+ | { [key: string]: SerializableValue }
111
+ | Literal
112
+ | SerializableValue[];
113
+
114
+ export type ServerOptions<T extends CoolorsMCPSessionAuth> = {
115
+ authenticate?: (request: unknown) => Promise<T>;
116
+ health?: {
117
+ enabled?: boolean;
118
+ message?: string;
119
+ path?: string;
120
+ status?: number;
121
+ };
122
+ instructions?: string;
123
+ logger?: Logger;
124
+ name: string;
125
+ ping?: {
126
+ enabled?: boolean;
127
+ intervalMs?: number;
128
+ logLevel?: LoggingLevel;
129
+ };
130
+ roots?: {
131
+ enabled?: boolean;
132
+ };
133
+ utils?: {
134
+ formatInvalidParamsErrorMessage?: (
135
+ issues: readonly StandardSchemaV1.Issue[],
136
+ ) => string;
137
+ };
138
+ version: `${number}.${number}.${number}`;
139
+ };
140
+
141
+ export type TextContent = {
142
+ text: string;
143
+ type: "text";
144
+ };
145
+
146
+ export type Tool<
147
+ T extends CoolorsMCPSessionAuth,
148
+ Params extends ToolParameters = ToolParameters,
149
+ > = {
150
+ annotations?: {
151
+ streamingHint?: boolean;
152
+ } & ToolAnnotations;
153
+ canAccess?: (auth: T) => boolean;
154
+ description?: string;
155
+
156
+ execute: (
157
+ args: StandardSchemaV1.InferOutput<Params>,
158
+ context: Context<T>,
159
+ ) => Promise<
160
+ | AudioContent
161
+ | ContentResult
162
+ | ImageContent
163
+ | ResourceContent
164
+ | ResourceLink
165
+ | string
166
+ | TextContent
167
+ | void
168
+ >;
169
+ name: string;
170
+ parameters?: Params;
171
+ timeoutMs?: number;
172
+ };
173
+
174
+ export type ToolAnnotations = {
175
+ destructiveHint?: boolean;
176
+ idempotentHint?: boolean;
177
+ openWorldHint?: boolean;
178
+ readOnlyHint?: boolean;
179
+ title?: string;
180
+ };
181
+
182
+ export type ToolParameters = StandardSchemaV1;
package/src/utils.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Converts camelCase to snake_case for OAuth endpoint responses
3
+ */
4
+ export function camelToSnakeCase(str: string): string {
5
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
6
+ }
7
+
8
+ /**
9
+ * Converts an object with camelCase keys to snake_case keys
10
+ */
11
+ export function convertObjectToSnakeCase(
12
+ obj: Record<string, unknown>,
13
+ ): Record<string, unknown> {
14
+ const result: Record<string, unknown> = {};
15
+
16
+ for (const [key, value] of Object.entries(obj)) {
17
+ const snakeKey = camelToSnakeCase(key);
18
+ result[snakeKey] = value;
19
+ }
20
+
21
+ return result;
22
+ }