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 +96 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +290 -0
- package/package.json +32 -0
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
|
package/dist/index.d.ts
ADDED
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
|
+
}
|