@loggi87/mcp-custom-xs 1.0.2 → 1.0.5
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/index.js +120 -113
- package/package.json +9 -2
package/index.js
CHANGED
|
@@ -1,138 +1,145 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// index.js - MCP Server (@loggi87/mcp-custom-xs)
|
|
3
|
+
const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
4
|
+
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5
|
+
const { z } = require("zod");
|
|
2
6
|
const fs = require("fs");
|
|
3
7
|
const path = require("path");
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
run_tsc_before_finish: true
|
|
13
|
-
};
|
|
14
|
-
}
|
|
9
|
+
const RULES = {
|
|
10
|
+
max_lines_per_file: 80,
|
|
11
|
+
architecture: "atomic",
|
|
12
|
+
project_structure: "feature-based",
|
|
13
|
+
typescript_strict: true,
|
|
14
|
+
run_tsc_before_finish: true
|
|
15
|
+
};
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
function validateFile(filePath) {
|
|
18
|
+
try {
|
|
19
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
20
|
+
const lines = content.split("\n").length;
|
|
21
|
+
const errors = [];
|
|
22
|
+
|
|
23
|
+
if (lines > RULES.max_lines_per_file) {
|
|
24
|
+
errors.push({
|
|
25
|
+
type: "TOO_MANY_LINES",
|
|
26
|
+
message: `❌ ${lines} líneas (máx ${RULES.max_lines_per_file})`,
|
|
27
|
+
severity: "error"
|
|
28
|
+
});
|
|
29
|
+
}
|
|
21
30
|
|
|
22
|
-
|
|
31
|
+
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) {
|
|
32
|
+
if (!content.includes("// @ts-check") && !content.includes("strict")) {
|
|
23
33
|
errors.push({
|
|
24
|
-
type: "
|
|
25
|
-
message:
|
|
26
|
-
severity: "
|
|
34
|
+
type: "NO_TYPESCRIPT_STRICT",
|
|
35
|
+
message: "❌ Falta TypeScript strict mode",
|
|
36
|
+
severity: "warn",
|
|
37
|
+
suggestion: "Agrega 'strict': true en tsconfig.json"
|
|
27
38
|
});
|
|
28
39
|
}
|
|
40
|
+
}
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (filePath.includes("components")) {
|
|
42
|
-
const validDirs = ["/atoms/", "/molecules/", "/organisms/"];
|
|
43
|
-
if (!validDirs.some((dir) => filePath.includes(dir))) {
|
|
44
|
-
errors.push({
|
|
45
|
-
type: "WRONG_COMPONENT_STRUCTURE",
|
|
46
|
-
message: "❌ Componente no está en atomic/molecules/organisms",
|
|
47
|
-
severity: "error",
|
|
48
|
-
suggestion: "Estructura correcta: src/components/{atoms|molecules|organisms}/"
|
|
49
|
-
});
|
|
50
|
-
}
|
|
42
|
+
if (filePath.includes("components")) {
|
|
43
|
+
const validDirs = ["/atoms/", "/molecules/", "/organisms/"];
|
|
44
|
+
if (!validDirs.some((dir) => filePath.includes(dir))) {
|
|
45
|
+
errors.push({
|
|
46
|
+
type: "WRONG_COMPONENT_STRUCTURE",
|
|
47
|
+
message: "❌ Componente no está en atoms/molecules/organisms",
|
|
48
|
+
severity: "error",
|
|
49
|
+
suggestion: "Estructura correcta: src/components/{atoms|molecules|organisms}/"
|
|
50
|
+
});
|
|
51
51
|
}
|
|
52
|
+
}
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
return {
|
|
55
|
+
valid: errors.length === 0,
|
|
56
|
+
file: filePath,
|
|
57
|
+
lineCount: lines,
|
|
58
|
+
errors,
|
|
59
|
+
summary: errors.length === 0
|
|
59
60
|
? "✅ Archivo válido"
|
|
60
61
|
: `❌ ${errors.length} error(es) encontrado(s)`
|
|
61
|
-
};
|
|
62
|
-
} catch (error) {
|
|
63
|
-
return {
|
|
64
|
-
valid: false,
|
|
65
|
-
file: filePath,
|
|
66
|
-
errors: [{
|
|
67
|
-
type: "FILE_READ_ERROR",
|
|
68
|
-
message: `No se pudo leer el archivo: ${error.message}`,
|
|
69
|
-
severity: "error"
|
|
70
|
-
}]
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
validateFeature(featureDir) {
|
|
76
|
-
const results = [];
|
|
77
|
-
|
|
78
|
-
const walkDir = (dir) => {
|
|
79
|
-
try {
|
|
80
|
-
const files = fs.readdirSync(dir);
|
|
81
|
-
files.forEach(file => {
|
|
82
|
-
const filePath = path.join(dir, file);
|
|
83
|
-
const stat = fs.statSync(filePath);
|
|
84
|
-
|
|
85
|
-
if (stat.isDirectory() && !file.startsWith('.')) {
|
|
86
|
-
walkDir(filePath);
|
|
87
|
-
} else if (file.endsWith('.ts') || file.endsWith('.tsx') || file.endsWith('.js')) {
|
|
88
|
-
results.push(this.validateFile(filePath));
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
} catch (error) {
|
|
92
|
-
console.error(`Error walking ${dir}:`, error.message);
|
|
93
|
-
}
|
|
94
62
|
};
|
|
95
|
-
|
|
96
|
-
walkDir(featureDir);
|
|
97
|
-
|
|
98
|
-
const allValid = results.every(r => r.valid);
|
|
99
|
-
const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);
|
|
100
|
-
|
|
63
|
+
} catch (error) {
|
|
101
64
|
return {
|
|
102
|
-
valid:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
totalErrors,
|
|
106
|
-
files: results,
|
|
107
|
-
summary: allValid
|
|
108
|
-
? `✅ Feature completo válido (${results.length} archivos)`
|
|
109
|
-
: `❌ ${totalErrors} error(es) en ${results.length} archivo(s)`
|
|
65
|
+
valid: false,
|
|
66
|
+
file: filePath,
|
|
67
|
+
errors: [{ type: "FILE_READ_ERROR", message: error.message, severity: "error" }]
|
|
110
68
|
};
|
|
111
69
|
}
|
|
70
|
+
}
|
|
112
71
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
72
|
+
function validateFeature(featureDir) {
|
|
73
|
+
const results = [];
|
|
116
74
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
75
|
+
const walkDir = (dir) => {
|
|
76
|
+
try {
|
|
77
|
+
fs.readdirSync(dir).forEach((file) => {
|
|
78
|
+
const filePath = path.join(dir, file);
|
|
79
|
+
const stat = fs.statSync(filePath);
|
|
80
|
+
if (stat.isDirectory() && !file.startsWith(".")) {
|
|
81
|
+
walkDir(filePath);
|
|
82
|
+
} else if (/\.(ts|tsx|js)$/.test(file)) {
|
|
83
|
+
results.push(validateFile(filePath));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
} catch (e) {}
|
|
87
|
+
};
|
|
120
88
|
|
|
121
|
-
|
|
89
|
+
walkDir(featureDir);
|
|
122
90
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
91
|
+
const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);
|
|
92
|
+
return {
|
|
93
|
+
valid: totalErrors === 0,
|
|
94
|
+
featureDir,
|
|
95
|
+
filesValidated: results.length,
|
|
96
|
+
totalErrors,
|
|
97
|
+
files: results,
|
|
98
|
+
summary: totalErrors === 0
|
|
99
|
+
? `✅ Feature válido (${results.length} archivos)`
|
|
100
|
+
: `❌ ${totalErrors} error(es) en ${results.length} archivo(s)`
|
|
101
|
+
};
|
|
130
102
|
}
|
|
131
103
|
|
|
132
|
-
|
|
133
|
-
|
|
104
|
+
function getFormattedRules() {
|
|
105
|
+
return `# Reglas del proyecto (@loggi87/mcp-custom-xs)
|
|
134
106
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
107
|
+
1. **Max ${RULES.max_lines_per_file} líneas por archivo**
|
|
108
|
+
2. **Atomic Design** para componentes (atoms / molecules / organisms)
|
|
109
|
+
3. **Feature-based** project structure
|
|
110
|
+
4. **TypeScript Strict Mode**
|
|
111
|
+
5. **Ejecutar tsc** antes de terminar cualquier tarea`;
|
|
138
112
|
}
|
|
113
|
+
|
|
114
|
+
// Servidor MCP
|
|
115
|
+
const server = new McpServer({ name: "mcp-custom-xs", version: "1.0.3" });
|
|
116
|
+
|
|
117
|
+
server.tool(
|
|
118
|
+
"validate_file",
|
|
119
|
+
"Valida un archivo contra las reglas del proyecto (líneas, atomic design, TypeScript)",
|
|
120
|
+
{ filePath: z.string().describe("Ruta absoluta o relativa al archivo a validar") },
|
|
121
|
+
async ({ filePath }) => ({
|
|
122
|
+
content: [{ type: "text", text: JSON.stringify(validateFile(filePath), null, 2) }]
|
|
123
|
+
})
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
server.tool(
|
|
127
|
+
"validate_feature",
|
|
128
|
+
"Valida todos los archivos de un directorio/feature contra las reglas del proyecto",
|
|
129
|
+
{ featureDir: z.string().describe("Ruta al directorio del feature a validar") },
|
|
130
|
+
async ({ featureDir }) => ({
|
|
131
|
+
content: [{ type: "text", text: JSON.stringify(validateFeature(featureDir), null, 2) }]
|
|
132
|
+
})
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
server.tool(
|
|
136
|
+
"get_rules",
|
|
137
|
+
"Devuelve las reglas de código del proyecto",
|
|
138
|
+
{},
|
|
139
|
+
async () => ({
|
|
140
|
+
content: [{ type: "text", text: getFormattedRules() }]
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const transport = new StdioServerTransport();
|
|
145
|
+
server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loggi87/mcp-custom-xs",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -10,5 +10,12 @@
|
|
|
10
10
|
"mcp",
|
|
11
11
|
"rules",
|
|
12
12
|
"atomic-design"
|
|
13
|
-
]
|
|
13
|
+
],
|
|
14
|
+
"bin": {
|
|
15
|
+
"mcp-custom-xs": "./index.js"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
19
|
+
"zod": "^4.3.6"
|
|
20
|
+
}
|
|
14
21
|
}
|