@trishchuk/coolors-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +39 -0
- package/.env +2 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +73 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +71 -0
- package/.github/pull_request_template.md +97 -0
- package/.github/workflows/ci.yml +127 -0
- package/.github/workflows/deploy-docs.yml +56 -0
- package/.github/workflows/release.yml +99 -0
- package/.mcp.json +12 -0
- package/.prettierignore +1 -0
- package/CLAUDE.md +201 -0
- package/DOCUMENTATION.md +274 -0
- package/GEMINI.md +54 -0
- package/LICENSE +21 -0
- package/README.md +401 -0
- package/demo/content_based_color.png +0 -0
- package/demo/music-player.html +621 -0
- package/demo/podcast-player.html +903 -0
- package/dist/bin/coolors-mcp.d.ts +1 -0
- package/dist/bin/coolors-mcp.js +154 -0
- package/dist/bin/coolors-mcp.js.map +1 -0
- package/dist/bin/server.d.ts +1 -0
- package/dist/bin/server.js +3292 -0
- package/dist/bin/server.js.map +1 -0
- package/dist/chunk-IQ7NN26V.js +114 -0
- package/dist/chunk-IQ7NN26V.js.map +1 -0
- package/dist/chunk-P3ARRKLS.js +1214 -0
- package/dist/chunk-P3ARRKLS.js.map +1 -0
- package/dist/color/index.d.ts +716 -0
- package/dist/color/index.js +153 -0
- package/dist/color/index.js.map +1 -0
- package/dist/coolors-mcp.d.ts +136 -0
- package/dist/coolors-mcp.js +7 -0
- package/dist/coolors-mcp.js.map +1 -0
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js +93 -0
- package/docs/.vitepress/cache/deps/@braintree_sanitize-url.js.map +7 -0
- package/docs/.vitepress/cache/deps/_metadata.json +127 -0
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js +9 -0
- package/docs/.vitepress/cache/deps/chunk-BUSYA2B4.js.map +7 -0
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js +12683 -0
- package/docs/.vitepress/cache/deps/chunk-JD3CXNQ6.js.map +7 -0
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js +9719 -0
- package/docs/.vitepress/cache/deps/chunk-SYPOPCWC.js.map +7 -0
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js +4710 -0
- package/docs/.vitepress/cache/deps/cytoscape-cose-bilkent.js.map +7 -0
- package/docs/.vitepress/cache/deps/cytoscape.js +30278 -0
- package/docs/.vitepress/cache/deps/cytoscape.js.map +7 -0
- package/docs/.vitepress/cache/deps/dayjs.js +285 -0
- package/docs/.vitepress/cache/deps/dayjs.js.map +7 -0
- package/docs/.vitepress/cache/deps/debug.js +468 -0
- package/docs/.vitepress/cache/deps/debug.js.map +7 -0
- package/docs/.vitepress/cache/deps/package.json +3 -0
- package/docs/.vitepress/cache/deps/prismjs.js +1466 -0
- package/docs/.vitepress/cache/deps/prismjs.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js +228 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-bash.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js +142 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-javascript.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js +27 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-json.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js +65 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-python.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js +53 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-typescript.js.map +7 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js +73 -0
- package/docs/.vitepress/cache/deps/prismjs_components_prism-yaml.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4507 -0
- package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +584 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js +1146 -0
- package/docs/.vitepress/cache/deps/vitepress___@vueuse_integrations_useFocusTrap.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js +1667 -0
- package/docs/.vitepress/cache/deps/vitepress___mark__js_src_vanilla__js.js.map +7 -0
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js +1814 -0
- package/docs/.vitepress/cache/deps/vitepress___minisearch.js.map +7 -0
- package/docs/.vitepress/cache/deps/vue.js +344 -0
- package/docs/.vitepress/cache/deps/vue.js.map +7 -0
- package/docs/.vitepress/components/ClientGrid.vue +125 -0
- package/docs/.vitepress/components/CodeBlock.vue +231 -0
- package/docs/.vitepress/components/ConfigModal.vue +477 -0
- package/docs/.vitepress/components/DiagramModal.vue +528 -0
- package/docs/.vitepress/components/TroubleshootingModal.vue +472 -0
- package/docs/.vitepress/config.js +162 -0
- package/docs/.vitepress/theme/FundingLayout.vue +251 -0
- package/docs/.vitepress/theme/Layout.vue +134 -0
- package/docs/.vitepress/theme/components/AdBanner.vue +317 -0
- package/docs/.vitepress/theme/components/AdPlaceholder.vue +78 -0
- package/docs/.vitepress/theme/components/FundingEffects.vue +322 -0
- package/docs/.vitepress/theme/components/FundingHero.vue +345 -0
- package/docs/.vitepress/theme/components/SupportSection.vue +511 -0
- package/docs/.vitepress/theme/custom-app.css +339 -0
- package/docs/.vitepress/theme/custom.css +699 -0
- package/docs/.vitepress/theme/index.js +25 -0
- package/docs/README.md +198 -0
- package/docs/concepts/accessibility.md +473 -0
- package/docs/concepts/color-spaces.md +222 -0
- package/docs/concepts/distance-metrics.md +384 -0
- package/docs/concepts/hct.md +261 -0
- package/docs/concepts/image-analysis.md +396 -0
- package/docs/concepts/material-design.md +306 -0
- package/docs/concepts/theme-matching.md +399 -0
- package/docs/examples/basic-colors.md +490 -0
- package/docs/examples/creating-themes.md +898 -0
- package/docs/examples/css-refactoring.md +824 -0
- package/docs/examples/image-extraction.md +882 -0
- package/docs/getting-started.md +366 -0
- package/docs/index.md +190 -0
- package/docs/installation.md +157 -0
- package/docs/tools/README.md +234 -0
- package/docs/tools/accessibility.md +614 -0
- package/docs/tools/color-operations.md +374 -0
- package/docs/tools/image-extraction.md +624 -0
- package/docs/tools/material-design.md +347 -0
- package/docs/tools/theme-matching.md +552 -0
- package/eslint.config.ts +14 -0
- package/examples/theme-matching.md +113 -0
- package/jsr.json +7 -0
- package/mcp-config.json +8 -0
- package/note.md +35 -0
- package/package.json +122 -0
- package/research_results.md +53 -0
- package/src/bin/coolors-mcp.ts +194 -0
- package/src/bin/server.ts +61 -0
- package/src/color/__tests__/conversions-argb.test.ts +198 -0
- package/src/color/__tests__/extract-colors.test.ts +360 -0
- package/src/color/__tests__/image-utils.test.ts +242 -0
- package/src/color/__tests__/reference-colors.test.ts +278 -0
- package/src/color/__tests__/round-trip.test.ts +197 -0
- package/src/color/conversions.test.ts +402 -0
- package/src/color/conversions.ts +393 -0
- package/src/color/dislike/__tests__/dislike-analyzer.test.ts +248 -0
- package/src/color/dislike/dislike-analyzer.ts +114 -0
- package/src/color/extract-colors.ts +228 -0
- package/src/color/hct/__tests__/hct-class.test.ts +232 -0
- package/src/color/hct/harmonization.ts +204 -0
- package/src/color/hct/hct-class.ts +109 -0
- package/src/color/hct/hct-solver.ts +168 -0
- package/src/color/hct/index.ts +39 -0
- package/src/color/hct/tonal-palette.ts +211 -0
- package/src/color/hct/types.ts +88 -0
- package/src/color/image-utils.ts +79 -0
- package/src/color/index.ts +87 -0
- package/src/color/material-theme.ts +157 -0
- package/src/color/metrics.test.ts +276 -0
- package/src/color/metrics.ts +281 -0
- package/src/color/quantize/__tests__/quantizer_celebi.test.ts +195 -0
- package/src/color/quantize/lab_point_provider.ts +55 -0
- package/src/color/quantize/point_provider.ts +27 -0
- package/src/color/quantize/quantizer_celebi.ts +51 -0
- package/src/color/quantize/quantizer_celebi_test.ts +71 -0
- package/src/color/quantize/quantizer_map.ts +47 -0
- package/src/color/quantize/quantizer_wsmeans.ts +232 -0
- package/src/color/quantize/quantizer_wu.ts +472 -0
- package/src/color/score/__tests__/score.test.ts +224 -0
- package/src/color/score/score.ts +175 -0
- package/src/color/types.ts +151 -0
- package/src/color/utils/color_utils.ts +292 -0
- package/src/color/utils/math_utils.ts +145 -0
- package/src/color/utils.test.ts +403 -0
- package/src/color/utils.ts +315 -0
- package/src/constants.ts +5 -0
- package/src/coolors-mcp.ts +37 -0
- package/src/examples/addition.ts +333 -0
- package/src/examples/color-demo.ts +125 -0
- package/src/examples/custom-logger.ts +201 -0
- package/src/examples/oauth-server.ts +113 -0
- package/src/examples/session-context.ts +269 -0
- package/src/session.ts +116 -0
- package/src/theme/__tests__/matcher.test.ts +180 -0
- package/src/theme/__tests__/parser.test.ts +148 -0
- package/src/theme/__tests__/refactor.test.ts +224 -0
- package/src/theme/index.ts +34 -0
- package/src/theme/matcher.ts +395 -0
- package/src/theme/parser.ts +392 -0
- package/src/theme/refactor.ts +360 -0
- package/src/theme/types.ts +152 -0
- package/src/tools/__tests__/gradient-generator.test.ts +206 -0
- package/src/tools/__tests__/palette-with-locks.test.ts +109 -0
- package/src/tools/color-conversion.tool.ts +54 -0
- package/src/tools/color-distance.tool.ts +41 -0
- package/src/tools/colors.ts +31 -0
- package/src/tools/contrast-checker.tool.ts +37 -0
- package/src/tools/dislike-analyzer.tool.ts +247 -0
- package/src/tools/gradient-generator.tool.ts +250 -0
- package/src/tools/image-extraction.tools.ts +289 -0
- package/src/tools/index.ts +39 -0
- package/src/tools/material-theme.tools.ts +250 -0
- package/src/tools/palette-generator.tool.ts +135 -0
- package/src/tools/palette-with-locks.tool.ts +221 -0
- package/src/tools/registry.ts +142 -0
- package/src/tools/simple-tools.ts +37 -0
- package/src/tools/theme-matching.tools.ts +334 -0
- package/src/types.ts +182 -0
- package/src/utils.ts +22 -0
- package/tsconfig.json +8 -0
- package/vitest.config.js +15 -0
|
@@ -0,0 +1,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
|
+
}
|