codictor-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/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # Codictor MCP
2
+
3
+ Convert screenshots to React + Tailwind code that uses YOUR project's design system.
4
+
5
+ ## Features
6
+
7
+ - **Auto-reads tailwind.config.js** - Uses your exact colors, fonts, spacing
8
+ - **Detects existing components** - Reuses your Button, Card, etc.
9
+ - **True responsive code** - Mobile-first with proper breakpoints
10
+ - **Claude Vision powered** - High-quality code generation
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install -g codictor-mcp
16
+ ```
17
+
18
+ Or install locally:
19
+
20
+ ```bash
21
+ npm install
22
+ npm run build
23
+ ```
24
+
25
+ ## Setup
26
+
27
+ Add to your IDE's MCP config:
28
+
29
+ ### Cursor (~/.cursor/mcp.json)
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "codictor": {
35
+ "command": "codictor-mcp",
36
+ "env": {
37
+ "ANTHROPIC_API_KEY": "sk-ant-your-key-here"
38
+ }
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ ### Claude Code
45
+
46
+ ```bash
47
+ claude mcp add codictor codictor-mcp -e ANTHROPIC_API_KEY=sk-ant-your-key-here
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ In Cursor or Claude Code:
53
+
54
+ ```
55
+ Convert this screenshot to code: /path/to/screenshot.png
56
+ ```
57
+
58
+ Or:
59
+
60
+ ```
61
+ Analyze my design system
62
+ ```
63
+
64
+ ## Tools
65
+
66
+ ### screenshot_to_code
67
+
68
+ Converts a screenshot to React + Tailwind code.
69
+
70
+ - `image_path` (required): Path to screenshot
71
+ - `project_path` (optional): Project root directory
72
+
73
+ ### analyze_design_system
74
+
75
+ Shows what design tokens and components will be used.
76
+
77
+ - `project_path` (optional): Project root directory
78
+
79
+ ## Environment Variables
80
+
81
+ The server supports these environment variables:
82
+
83
+ - `ANTHROPIC_API_KEY` - Your Anthropic API key
84
+ - `AI_INTEGRATIONS_ANTHROPIC_API_KEY` - Replit AI Integrations key (preferred on Replit)
85
+ - `AI_INTEGRATIONS_ANTHROPIC_BASE_URL` - Replit AI Integrations base URL
86
+
87
+ ## How It Works
88
+
89
+ 1. Reads your `tailwind.config.js` to understand your design tokens
90
+ 2. Scans common component directories to find existing components
91
+ 3. Sends the screenshot to Claude Vision with your design context
92
+ 4. Returns production-ready React + Tailwind code using YOUR design system
93
+
94
+ ## License
95
+
96
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,290 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import Anthropic from "@anthropic-ai/sdk";
6
+ import * as fs from "fs";
7
+ import * as path from "path";
8
+ const anthropic = new Anthropic({
9
+ apiKey: process.env.AI_INTEGRATIONS_ANTHROPIC_API_KEY || process.env.ANTHROPIC_API_KEY,
10
+ ...(process.env.AI_INTEGRATIONS_ANTHROPIC_BASE_URL && {
11
+ baseURL: process.env.AI_INTEGRATIONS_ANTHROPIC_BASE_URL,
12
+ }),
13
+ });
14
+ function buildSystemPrompt(tailwindConfig, existingComponents) {
15
+ const configSection = tailwindConfig.trim()
16
+ ? `The user's tailwind.config.js:
17
+ \`\`\`javascript
18
+ ${tailwindConfig}
19
+ \`\`\`
20
+ Use their EXACT color names, font families, spacing, and border-radius values. If they define \`primary\` use \`bg-primary\` not \`bg-blue-500\`. If they define custom fonts, use those font classes.`
21
+ : "No custom config provided. Use standard Tailwind defaults.";
22
+ const componentsSection = existingComponents.length > 0
23
+ ? `\n\nEXISTING COMPONENTS IN PROJECT:
24
+ The user already has these components: ${existingComponents.join(", ")}
25
+ Import and USE these existing components instead of creating new ones with the same purpose.`
26
+ : "";
27
+ return `You are Codictor, an expert frontend developer converting screenshots to production-ready React + Tailwind code.
28
+
29
+ THREE REQUIREMENTS:
30
+
31
+ 1. USE THE USER'S DESIGN SYSTEM
32
+ ${configSection}
33
+
34
+ 2. EXTRACT REUSABLE COMPONENTS
35
+ Identify UI patterns and create named React components:
36
+ - Buttons as <Button variant="primary|secondary|ghost" size="sm|md|lg">
37
+ - Cards as <Card title="" subtitle="" image="">
38
+ - Inputs as <Input label="" placeholder="" type="">
39
+ - Navigation as <NavLink href="" active="">
40
+ - Any repeated patterns as named components with props
41
+
42
+ Structure output as: First define all extracted components, then the main page/layout using those components.
43
+ ${componentsSection}
44
+
45
+ 3. TRUE RESPONSIVE CODE (Mobile-First)
46
+ Generate code that works perfectly at THREE breakpoints:
47
+ - Phone (375px): base styles, single column, larger touch targets
48
+ - Tablet (768px): md: prefix, 2 columns where appropriate
49
+ - Desktop (1280px): lg: and xl: prefixes, full layout
50
+
51
+ ALWAYS include responsive classes for:
52
+ - Layout: flex-col md:flex-row, grid-cols-1 md:grid-cols-2 lg:grid-cols-3
53
+ - Spacing: p-4 md:p-6 lg:p-8, gap-4 md:gap-6
54
+ - Typography: text-sm md:text-base lg:text-lg
55
+ - Visibility: hidden md:block, md:hidden
56
+
57
+ OUTPUT FORMAT: Return ONLY valid JSX code. No markdown, no explanation, no code fence blocks. Start directly with component definitions, end with the default export. Make it pixel-perfect to the screenshot while being fully responsive.`;
58
+ }
59
+ function findTailwindConfig(projectPath) {
60
+ const possiblePaths = [
61
+ "tailwind.config.js",
62
+ "tailwind.config.ts",
63
+ "tailwind.config.mjs",
64
+ "tailwind.config.cjs",
65
+ ];
66
+ for (const configFile of possiblePaths) {
67
+ const fullPath = path.join(projectPath, configFile);
68
+ if (fs.existsSync(fullPath)) {
69
+ try {
70
+ return fs.readFileSync(fullPath, "utf-8");
71
+ }
72
+ catch (e) {
73
+ console.error(`Error reading ${fullPath}:`, e);
74
+ }
75
+ }
76
+ }
77
+ return "";
78
+ }
79
+ function findExistingComponents(projectPath) {
80
+ const components = [];
81
+ const componentDirs = [
82
+ "components",
83
+ "src/components",
84
+ "app/components",
85
+ "lib/components",
86
+ ];
87
+ for (const dir of componentDirs) {
88
+ const fullPath = path.join(projectPath, dir);
89
+ if (fs.existsSync(fullPath)) {
90
+ try {
91
+ const files = fs.readdirSync(fullPath);
92
+ for (const file of files) {
93
+ const match = file.match(/^([A-Z][a-zA-Z0-9]*)\.(tsx?|jsx?)$/);
94
+ if (match) {
95
+ components.push(match[1]);
96
+ }
97
+ }
98
+ }
99
+ catch (e) {
100
+ console.error(`Error reading ${fullPath}:`, e);
101
+ }
102
+ }
103
+ }
104
+ return [...new Set(components)];
105
+ }
106
+ function readImageAsBase64(imagePath) {
107
+ const absolutePath = path.resolve(imagePath);
108
+ if (!fs.existsSync(absolutePath)) {
109
+ throw new Error(`Image file not found: ${absolutePath}`);
110
+ }
111
+ const buffer = fs.readFileSync(absolutePath);
112
+ const base64 = buffer.toString("base64");
113
+ const ext = path.extname(imagePath).toLowerCase();
114
+ const mediaTypes = {
115
+ ".png": "image/png",
116
+ ".jpg": "image/jpeg",
117
+ ".jpeg": "image/jpeg",
118
+ ".webp": "image/webp",
119
+ ".gif": "image/gif",
120
+ };
121
+ const mediaType = mediaTypes[ext] || "image/png";
122
+ return { data: base64, mediaType };
123
+ }
124
+ async function convertScreenshotToCode(imagePath, projectPath = process.cwd()) {
125
+ const tailwindConfig = findTailwindConfig(projectPath);
126
+ const existingComponents = findExistingComponents(projectPath);
127
+ const { data: base64Data, mediaType } = readImageAsBase64(imagePath);
128
+ const response = await anthropic.messages.create({
129
+ model: "claude-sonnet-4-5",
130
+ max_tokens: 8000,
131
+ system: buildSystemPrompt(tailwindConfig, existingComponents),
132
+ messages: [
133
+ {
134
+ role: "user",
135
+ content: [
136
+ {
137
+ type: "image",
138
+ source: {
139
+ type: "base64",
140
+ media_type: mediaType,
141
+ data: base64Data,
142
+ },
143
+ },
144
+ {
145
+ type: "text",
146
+ text: "Convert this screenshot to React + Tailwind code following the system instructions.",
147
+ },
148
+ ],
149
+ },
150
+ ],
151
+ });
152
+ const textBlock = response.content.find((block) => block.type === "text");
153
+ if (!textBlock || textBlock.type !== "text") {
154
+ throw new Error("No text response from Claude");
155
+ }
156
+ let code = textBlock.text
157
+ .replace(/^```(?:jsx|javascript|tsx|js)?\n?/gm, "")
158
+ .replace(/```$/gm, "")
159
+ .trim();
160
+ return code;
161
+ }
162
+ const server = new Server({
163
+ name: "codictor-mcp",
164
+ version: "1.0.0",
165
+ }, {
166
+ capabilities: {
167
+ tools: {},
168
+ },
169
+ });
170
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
171
+ return {
172
+ tools: [
173
+ {
174
+ name: "screenshot_to_code",
175
+ description: "Convert a screenshot to React + Tailwind code using your project's design system. Automatically reads your tailwind.config.js and existing components.",
176
+ inputSchema: {
177
+ type: "object",
178
+ properties: {
179
+ image_path: {
180
+ type: "string",
181
+ description: "Path to the screenshot image file (PNG, JPG, or WebP)",
182
+ },
183
+ project_path: {
184
+ type: "string",
185
+ description: "Path to the project root (optional, defaults to current directory). Used to find tailwind.config.js and existing components.",
186
+ },
187
+ },
188
+ required: ["image_path"],
189
+ },
190
+ },
191
+ {
192
+ name: "analyze_design_system",
193
+ description: "Analyze the current project's design system without converting a screenshot. Shows what colors, fonts, and components will be used.",
194
+ inputSchema: {
195
+ type: "object",
196
+ properties: {
197
+ project_path: {
198
+ type: "string",
199
+ description: "Path to the project root (optional, defaults to current directory)",
200
+ },
201
+ },
202
+ },
203
+ },
204
+ ],
205
+ };
206
+ });
207
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
208
+ const { name, arguments: args } = request.params;
209
+ if (name === "screenshot_to_code") {
210
+ const imagePath = args?.image_path;
211
+ const projectPath = args?.project_path || process.cwd();
212
+ if (!imagePath) {
213
+ return {
214
+ content: [
215
+ {
216
+ type: "text",
217
+ text: "Error: image_path is required",
218
+ },
219
+ ],
220
+ };
221
+ }
222
+ try {
223
+ const code = await convertScreenshotToCode(imagePath, projectPath);
224
+ return {
225
+ content: [
226
+ {
227
+ type: "text",
228
+ text: code,
229
+ },
230
+ ],
231
+ };
232
+ }
233
+ catch (error) {
234
+ const message = error instanceof Error ? error.message : String(error);
235
+ return {
236
+ content: [
237
+ {
238
+ type: "text",
239
+ text: `Error: ${message}`,
240
+ },
241
+ ],
242
+ };
243
+ }
244
+ }
245
+ if (name === "analyze_design_system") {
246
+ const projectPath = args?.project_path || process.cwd();
247
+ const tailwindConfig = findTailwindConfig(projectPath);
248
+ const existingComponents = findExistingComponents(projectPath);
249
+ let analysis = "# Design System Analysis\n\n";
250
+ if (tailwindConfig) {
251
+ analysis += "## Tailwind Config Found ✓\n\n";
252
+ analysis += "```javascript\n" + tailwindConfig + "\n```\n\n";
253
+ }
254
+ else {
255
+ analysis += "## No Tailwind Config Found\n\n";
256
+ analysis += "Will use standard Tailwind defaults.\n\n";
257
+ }
258
+ if (existingComponents.length > 0) {
259
+ analysis += "## Existing Components Found ✓\n\n";
260
+ analysis += existingComponents.map((c) => `- ${c}`).join("\n");
261
+ analysis += "\n\nThese components will be imported and reused.\n";
262
+ }
263
+ else {
264
+ analysis += "## No Existing Components Found\n\n";
265
+ analysis += "New components will be created as needed.\n";
266
+ }
267
+ return {
268
+ content: [
269
+ {
270
+ type: "text",
271
+ text: analysis,
272
+ },
273
+ ],
274
+ };
275
+ }
276
+ return {
277
+ content: [
278
+ {
279
+ type: "text",
280
+ text: `Unknown tool: ${name}`,
281
+ },
282
+ ],
283
+ };
284
+ });
285
+ async function main() {
286
+ const transport = new StdioServerTransport();
287
+ await server.connect(transport);
288
+ console.error("Codictor MCP server running on stdio");
289
+ }
290
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "codictor-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Screenshot to code MCP that uses your project's design system",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "bin": {
9
+ "codictor-mcp": "./dist/index.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "start": "node dist/index.js",
14
+ "dev": "tsc && node dist/index.js"
15
+ },
16
+ "keywords": [
17
+ "mcp",
18
+ "screenshot",
19
+ "tailwind",
20
+ "react",
21
+ "code-generation"
22
+ ],
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "@anthropic-ai/sdk": "^0.71.2",
26
+ "@modelcontextprotocol/sdk": "^1.25.3"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^20.19.30",
30
+ "typescript": "^5.9.3"
31
+ }
32
+ }