@devstroupe/devkit-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 +92 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +353 -0
- package/dist/integration.test.d.ts +1 -0
- package/dist/integration.test.js +159 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# @devstroupe/devkit-mcp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@devstroupe/devkit-mcp)
|
|
4
|
+
[](../../LICENSE)
|
|
5
|
+
|
|
6
|
+
Servidor **Model Context Protocol (MCP)** do DT-DevKit. Expõe todas as automações da CLI como ferramentas nativas para agentes de IA (Cursor, VS Code, Claude Desktop, Gemini, Windsurf e outros).
|
|
7
|
+
|
|
8
|
+
## Instalação
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g @devstroupe/devkit-mcp
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Ferramentas MCP disponíveis
|
|
15
|
+
|
|
16
|
+
| Ferramenta | Descrição |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `ds_get_config` | Retorna o `devstroupe.config.ts` completo formatado |
|
|
19
|
+
| `ds_list_entities` | Lista as entidades e propriedades configuradas |
|
|
20
|
+
| `ds_init_ui` | Inicializa o Design System Spartan NG no projeto Angular |
|
|
21
|
+
| `ds_generate_crud` | Gera CRUD completo (backend + frontend) para uma entidade |
|
|
22
|
+
| `ds_check_architecture` | Auditoria de conformidade com as regras de governança |
|
|
23
|
+
| `ds_set_spartan_style` | Troca o style visual do Spartan NG |
|
|
24
|
+
| `ds_new_project` | Cria um novo projeto DevKit integrado |
|
|
25
|
+
| `ds_generate_migration` | Gera migration TypeORM no backend |
|
|
26
|
+
| `ds_run_harnesses` | Roda os testes e harnesses do framework |
|
|
27
|
+
|
|
28
|
+
## Configuração nas IDEs
|
|
29
|
+
|
|
30
|
+
### Cursor / VS Code
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"mcpServers": {
|
|
34
|
+
"devkit": { "command": "devstroupe-mcp" }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Claude Desktop
|
|
40
|
+
Arquivo: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) ou `%APPDATA%\Claude\claude_desktop_config.json` (Windows)
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"devkit": { "command": "devstroupe-mcp" }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Gemini / Antigravity (Google IDE)
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"servers": {
|
|
53
|
+
"devkit": { "command": "devstroupe-mcp" }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Windsurf
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"devkit": { "command": "devstroupe-mcp" }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Monorepo local (desenvolvimento do framework)
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"devkit": {
|
|
72
|
+
"command": "node",
|
|
73
|
+
"args": ["/caminho/absoluto/para/packages/devkit-mcp/dist/index.js"]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Como o servidor resolve o CLI
|
|
80
|
+
|
|
81
|
+
O `devkit-mcp` tenta resolver automaticamente o `@devstroupe/devkit-cli`:
|
|
82
|
+
1. Primeiro via `require.resolve('@devstroupe/devkit-cli/dist/index.js')` — instalação npm
|
|
83
|
+
2. Fallback: path relativo `../../devkit-cli/dist/index.js` — monorepo local
|
|
84
|
+
|
|
85
|
+
## Harnesses para IAs
|
|
86
|
+
|
|
87
|
+
O `ds_run_harnesses` roda `pnpm test` no monorepo do DevKit, executando:
|
|
88
|
+
- Testes de regras do linter (`linter-rules.test.ts`)
|
|
89
|
+
- Testes de templates de relacionamentos (`relationship-templates.test.ts`)
|
|
90
|
+
- Testes de integração do MCP (`integration.test.ts`)
|
|
91
|
+
|
|
92
|
+
Use após modificar templates ou regras para garantir que o framework está funcionando corretamente.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const fs = __importStar(require("fs-extra"));
|
|
39
|
+
const readline = __importStar(require("readline"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
// Registrar ts-node para carregar a config TS
|
|
42
|
+
try {
|
|
43
|
+
require('ts-node').register({
|
|
44
|
+
compilerOptions: { module: 'commonjs' }
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
// Ignora se não houver ts-node
|
|
49
|
+
}
|
|
50
|
+
const rl = readline.createInterface({
|
|
51
|
+
input: process.stdin,
|
|
52
|
+
output: process.stdout,
|
|
53
|
+
terminal: false
|
|
54
|
+
});
|
|
55
|
+
const SPARTAN_STYLES = ['nova', 'vega', 'lyra', 'maia', 'mira', 'luma'];
|
|
56
|
+
/**
|
|
57
|
+
* Resolve o caminho do CLI do devstroupe.
|
|
58
|
+
* Quando instalado via npm: usa require.resolve('@devstroupe/devkit-cli/dist/index.js')
|
|
59
|
+
* Em desenvolvimento local (monorepo): usa path relativo
|
|
60
|
+
*/
|
|
61
|
+
function resolveCliPath() {
|
|
62
|
+
// Tenta resolver via npm (instalado como dependência)
|
|
63
|
+
try {
|
|
64
|
+
return require.resolve('@devstroupe/devkit-cli/dist/index.js');
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Fallback: desenvolvimento local no monorepo
|
|
68
|
+
return path.resolve(__dirname, '../../devkit-cli/dist/index.js');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function sendResponse(response) {
|
|
72
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
73
|
+
}
|
|
74
|
+
function sendError(id, code, message) {
|
|
75
|
+
sendResponse({ jsonrpc: '2.0', id, error: { code, message } });
|
|
76
|
+
}
|
|
77
|
+
function sendResult(id, result) {
|
|
78
|
+
sendResponse({ jsonrpc: '2.0', id, result });
|
|
79
|
+
}
|
|
80
|
+
rl.on('line', (line) => {
|
|
81
|
+
if (!line.trim())
|
|
82
|
+
return;
|
|
83
|
+
try {
|
|
84
|
+
const request = JSON.parse(line);
|
|
85
|
+
if (request.id === undefined)
|
|
86
|
+
return; // notificações
|
|
87
|
+
handleRequest(request);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
sendError(null, -32700, 'Parse error');
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
function handleRequest(request) {
|
|
94
|
+
const { id, method, params } = request;
|
|
95
|
+
switch (method) {
|
|
96
|
+
case 'initialize':
|
|
97
|
+
sendResult(id, {
|
|
98
|
+
protocolVersion: '2024-11-05',
|
|
99
|
+
capabilities: { tools: {} },
|
|
100
|
+
serverInfo: { name: 'devkit-mcp', version: '1.0.0' }
|
|
101
|
+
});
|
|
102
|
+
break;
|
|
103
|
+
case 'tools/list':
|
|
104
|
+
sendResult(id, {
|
|
105
|
+
tools: [
|
|
106
|
+
{
|
|
107
|
+
name: 'ds_list_entities',
|
|
108
|
+
description: 'Lista todas as entidades e propriedades configuradas no devstroupe.config.ts do projeto.',
|
|
109
|
+
inputSchema: { type: 'object', properties: {} }
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'ds_get_config',
|
|
113
|
+
description: 'Retorna o conteúdo completo e formatado do devstroupe.config.ts. Use antes de gerar CRUDs ou tomar decisões arquiteturais.',
|
|
114
|
+
inputSchema: { type: 'object', properties: {} }
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'ds_generate_crud',
|
|
118
|
+
description: 'Gera backend NestJS + frontend Angular completos (controller, service, repository, module, list, form) para uma entidade configurada no devstroupe.config.ts.',
|
|
119
|
+
inputSchema: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: {
|
|
122
|
+
entityName: {
|
|
123
|
+
type: 'string',
|
|
124
|
+
description: 'Nome da entidade conforme declarado no config (ex: cabin, booking, user)'
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
required: ['entityName']
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'ds_check_architecture',
|
|
132
|
+
description: 'Executa a auditoria estática de governança arquitetural (linter) e retorna o relatório detalhado de violações. Use após gerar código para garantir conformidade.',
|
|
133
|
+
inputSchema: { type: 'object', properties: {} }
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'ds_init_ui',
|
|
137
|
+
description: 'Inicializa o Design System Angular com primitivos reais do Spartan Helm (libs/ui), injeta wrappers devkit-*, ThemeService, ProfileComponent e layout. Obrigatório antes de gerar CRUDs no frontend.',
|
|
138
|
+
inputSchema: {
|
|
139
|
+
type: 'object',
|
|
140
|
+
properties: {
|
|
141
|
+
style: {
|
|
142
|
+
type: 'string',
|
|
143
|
+
enum: ['nova', 'vega', 'lyra', 'maia', 'mira', 'luma'],
|
|
144
|
+
description: 'Style visual do Spartan NG. Padrão: vega'
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'ds_set_spartan_style',
|
|
151
|
+
description: 'Altera o style visual do Spartan NG e regenera todos os primitivos Helm em libs/ui. Atenção: não misture styles no mesmo app.',
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: 'object',
|
|
154
|
+
properties: {
|
|
155
|
+
style: {
|
|
156
|
+
type: 'string',
|
|
157
|
+
enum: ['nova', 'vega', 'lyra', 'maia', 'mira', 'luma'],
|
|
158
|
+
description: 'Novo style Spartan'
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
required: ['style']
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'ds_new_project',
|
|
166
|
+
description: 'Cria um novo projeto integrado NestJS + Angular com o DevKit já configurado. Deve ser rodado na pasta onde o projeto será criado.',
|
|
167
|
+
inputSchema: {
|
|
168
|
+
type: 'object',
|
|
169
|
+
properties: {
|
|
170
|
+
projectName: {
|
|
171
|
+
type: 'string',
|
|
172
|
+
description: 'Nome do projeto (ex: minha-app)'
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
required: ['projectName']
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'ds_generate_migration',
|
|
180
|
+
description: 'Gera uma migration TypeORM para o backend do projeto. Execute após criar ou modificar entidades para manter o schema do banco sincronizado.',
|
|
181
|
+
inputSchema: {
|
|
182
|
+
type: 'object',
|
|
183
|
+
properties: {
|
|
184
|
+
backendPath: {
|
|
185
|
+
type: 'string',
|
|
186
|
+
description: 'Caminho para a pasta raiz do backend NestJS (padrão: ./backend)'
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'ds_run_harnesses',
|
|
193
|
+
description: 'Executa os testes e harnesses do framework DevKit para validar que o código gerado e as regras de governança estão funcionando corretamente.',
|
|
194
|
+
inputSchema: { type: 'object', properties: {} }
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
});
|
|
198
|
+
break;
|
|
199
|
+
case 'tools/call':
|
|
200
|
+
if (!params || !params.name) {
|
|
201
|
+
sendError(id, -32602, 'Invalid params: name is required');
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
handleToolCall(id, params.name, params.arguments || {});
|
|
205
|
+
break;
|
|
206
|
+
default:
|
|
207
|
+
sendError(id, -32601, 'Method not found');
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function handleToolCall(id, toolName, args) {
|
|
212
|
+
const configPath = path.join(process.cwd(), 'devstroupe.config.ts');
|
|
213
|
+
const cliPath = resolveCliPath();
|
|
214
|
+
// ── ds_list_entities ──────────────────────────────────────────────────────
|
|
215
|
+
if (toolName === 'ds_list_entities') {
|
|
216
|
+
if (!fs.existsSync(configPath)) {
|
|
217
|
+
return sendResult(id, {
|
|
218
|
+
content: [{ type: 'text', text: 'devstroupe.config.ts não encontrado. Rode "devstroupe init" primeiro.' }],
|
|
219
|
+
isError: true
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
delete require.cache[require.resolve(configPath)];
|
|
224
|
+
const importedConfig = require(configPath);
|
|
225
|
+
const config = importedConfig.default || importedConfig;
|
|
226
|
+
sendResult(id, {
|
|
227
|
+
content: [{ type: 'text', text: JSON.stringify(config.entities || [], null, 2) }]
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
sendResult(id, {
|
|
232
|
+
content: [{ type: 'text', text: `Erro ao carregar config: ${err.message}` }],
|
|
233
|
+
isError: true
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
// ── ds_get_config ─────────────────────────────────────────────────────────
|
|
237
|
+
}
|
|
238
|
+
else if (toolName === 'ds_get_config') {
|
|
239
|
+
if (!fs.existsSync(configPath)) {
|
|
240
|
+
return sendResult(id, {
|
|
241
|
+
content: [{ type: 'text', text: 'devstroupe.config.ts não encontrado. Rode "devstroupe init" primeiro.' }],
|
|
242
|
+
isError: true
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
delete require.cache[require.resolve(configPath)];
|
|
247
|
+
const importedConfig = require(configPath);
|
|
248
|
+
const config = importedConfig.default || importedConfig;
|
|
249
|
+
sendResult(id, {
|
|
250
|
+
content: [{ type: 'text', text: JSON.stringify(config, null, 2) }]
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
sendResult(id, {
|
|
255
|
+
content: [{ type: 'text', text: `Erro ao carregar config: ${err.message}` }],
|
|
256
|
+
isError: true
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
// ── ds_generate_crud ──────────────────────────────────────────────────────
|
|
260
|
+
}
|
|
261
|
+
else if (toolName === 'ds_generate_crud') {
|
|
262
|
+
const { entityName } = args;
|
|
263
|
+
if (!entityName)
|
|
264
|
+
return sendError(id, -32602, 'entityName é obrigatório');
|
|
265
|
+
(0, child_process_1.exec)(`node "${cliPath}" generate crud "${entityName}"`, (err, stdout, stderr) => {
|
|
266
|
+
sendResult(id, {
|
|
267
|
+
content: [{ type: 'text', text: err ? `Falha: ${stderr || err.message}` : stdout || `CRUD de "${entityName}" gerado com sucesso!` }],
|
|
268
|
+
isError: !!err
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
// ── ds_check_architecture ─────────────────────────────────────────────────
|
|
272
|
+
}
|
|
273
|
+
else if (toolName === 'ds_check_architecture') {
|
|
274
|
+
(0, child_process_1.exec)(`node "${cliPath}" lint:rules`, (err, stdout, stderr) => {
|
|
275
|
+
const hasViolations = !!err && !stdout.includes('Varredura finalizada');
|
|
276
|
+
sendResult(id, {
|
|
277
|
+
content: [{ type: 'text', text: stdout || stderr || 'Auditoria concluída sem saída.' }],
|
|
278
|
+
isError: hasViolations
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
// ── ds_init_ui ────────────────────────────────────────────────────────────
|
|
282
|
+
}
|
|
283
|
+
else if (toolName === 'ds_init_ui') {
|
|
284
|
+
if (args.style && !SPARTAN_STYLES.includes(args.style)) {
|
|
285
|
+
return sendError(id, -32602, `Style inválido. Use: ${SPARTAN_STYLES.join(', ')}`);
|
|
286
|
+
}
|
|
287
|
+
const styleArg = args.style ? ` --style "${args.style}"` : '';
|
|
288
|
+
(0, child_process_1.exec)(`node "${cliPath}" init-ui${styleArg}`, (err, stdout, stderr) => {
|
|
289
|
+
sendResult(id, {
|
|
290
|
+
content: [{ type: 'text', text: err ? `Falha: ${stderr || err.message}` : stdout || 'Design system inicializado com sucesso.' }],
|
|
291
|
+
isError: !!err
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
// ── ds_set_spartan_style ──────────────────────────────────────────────────
|
|
295
|
+
}
|
|
296
|
+
else if (toolName === 'ds_set_spartan_style') {
|
|
297
|
+
const { style } = args;
|
|
298
|
+
if (!style)
|
|
299
|
+
return sendError(id, -32602, 'style é obrigatório');
|
|
300
|
+
if (!SPARTAN_STYLES.includes(style)) {
|
|
301
|
+
return sendError(id, -32602, `Style inválido. Use: ${SPARTAN_STYLES.join(', ')}`);
|
|
302
|
+
}
|
|
303
|
+
(0, child_process_1.exec)(`node "${cliPath}" ui:set-style "${style}"`, (err, stdout, stderr) => {
|
|
304
|
+
sendResult(id, {
|
|
305
|
+
content: [{ type: 'text', text: err ? `Falha: ${stderr || err.message}` : stdout || `Style alterado para ${style}.` }],
|
|
306
|
+
isError: !!err
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
// ── ds_new_project ────────────────────────────────────────────────────────
|
|
310
|
+
}
|
|
311
|
+
else if (toolName === 'ds_new_project') {
|
|
312
|
+
const { projectName } = args;
|
|
313
|
+
if (!projectName)
|
|
314
|
+
return sendError(id, -32602, 'projectName é obrigatório');
|
|
315
|
+
(0, child_process_1.exec)(`node "${cliPath}" new-project "${projectName}"`, { cwd: process.cwd() }, (err, stdout, stderr) => {
|
|
316
|
+
sendResult(id, {
|
|
317
|
+
content: [{ type: 'text', text: err ? `Falha: ${stderr || err.message}` : stdout || `Projeto "${projectName}" criado com sucesso!` }],
|
|
318
|
+
isError: !!err
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
// ── ds_generate_migration ─────────────────────────────────────────────────
|
|
322
|
+
}
|
|
323
|
+
else if (toolName === 'ds_generate_migration') {
|
|
324
|
+
const backendPath = args.backendPath || './backend';
|
|
325
|
+
const absoluteBackend = path.resolve(process.cwd(), backendPath);
|
|
326
|
+
if (!fs.existsSync(absoluteBackend)) {
|
|
327
|
+
return sendResult(id, {
|
|
328
|
+
content: [{ type: 'text', text: `Backend não encontrado em: ${absoluteBackend}` }],
|
|
329
|
+
isError: true
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
(0, child_process_1.exec)('npm run migration:generate', { cwd: absoluteBackend }, (err, stdout, stderr) => {
|
|
333
|
+
sendResult(id, {
|
|
334
|
+
content: [{ type: 'text', text: err ? `Falha na migration: ${stderr || err.message}` : stdout || 'Migration gerada com sucesso.' }],
|
|
335
|
+
isError: !!err
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
// ── ds_run_harnesses ──────────────────────────────────────────────────────
|
|
339
|
+
}
|
|
340
|
+
else if (toolName === 'ds_run_harnesses') {
|
|
341
|
+
// Tenta descobrir a raiz do monorepo DevKit instalado
|
|
342
|
+
const monorepoRoot = path.resolve(path.dirname(cliPath), '../../..');
|
|
343
|
+
(0, child_process_1.exec)('pnpm test', { cwd: monorepoRoot }, (err, stdout, stderr) => {
|
|
344
|
+
sendResult(id, {
|
|
345
|
+
content: [{ type: 'text', text: err ? `Testes falharam:\n${stderr || err.message}\n\n${stdout}` : stdout || 'Todos os harnesses passaram!' }],
|
|
346
|
+
isError: !!err
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
sendError(id, -32601, `Ferramenta não encontrada: ${toolName}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const node_test_1 = require("node:test");
|
|
37
|
+
const assert = __importStar(require("node:assert"));
|
|
38
|
+
const child_process_1 = require("child_process");
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
(0, node_test_1.test)('MCP Server Integration Test', async (t) => {
|
|
41
|
+
const serverPath = path.resolve(__dirname, 'index.js');
|
|
42
|
+
const sandboxDir = path.resolve(__dirname, '../../../sandbox-test');
|
|
43
|
+
const cp = (0, child_process_1.spawn)('node', [serverPath], {
|
|
44
|
+
cwd: sandboxDir,
|
|
45
|
+
stdio: ['pipe', 'pipe', 'inherit']
|
|
46
|
+
});
|
|
47
|
+
const responses = [];
|
|
48
|
+
let buffer = '';
|
|
49
|
+
const onData = new Promise((resolve) => {
|
|
50
|
+
cp.stdout.on('data', (chunk) => {
|
|
51
|
+
buffer += chunk.toString();
|
|
52
|
+
let index;
|
|
53
|
+
while ((index = buffer.indexOf('\n')) !== -1) {
|
|
54
|
+
const line = buffer.substring(0, index).trim();
|
|
55
|
+
buffer = buffer.substring(index + 1);
|
|
56
|
+
if (line) {
|
|
57
|
+
responses.push(line);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
cp.on('close', () => resolve());
|
|
62
|
+
});
|
|
63
|
+
async function sendRequest(req) {
|
|
64
|
+
cp.stdin.write(JSON.stringify(req) + '\n');
|
|
65
|
+
const startTime = Date.now();
|
|
66
|
+
while (Date.now() - startTime < 10000) { // 10s timeout
|
|
67
|
+
for (let i = 0; i < responses.length; i++) {
|
|
68
|
+
try {
|
|
69
|
+
const res = JSON.parse(responses[i]);
|
|
70
|
+
if (res.id === req.id) {
|
|
71
|
+
responses.splice(i, 1);
|
|
72
|
+
return res;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch { }
|
|
76
|
+
}
|
|
77
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
78
|
+
}
|
|
79
|
+
throw new Error(`Timeout waiting for response to request id ${req.id}`);
|
|
80
|
+
}
|
|
81
|
+
await t.test('should respond to initialize', async () => {
|
|
82
|
+
const res = await sendRequest({
|
|
83
|
+
jsonrpc: '2.0',
|
|
84
|
+
id: 1,
|
|
85
|
+
method: 'initialize',
|
|
86
|
+
params: { protocolVersion: '2024-11-05' }
|
|
87
|
+
});
|
|
88
|
+
assert.strictEqual(res.jsonrpc, '2.0');
|
|
89
|
+
assert.strictEqual(res.id, 1);
|
|
90
|
+
assert.ok(res.result);
|
|
91
|
+
assert.strictEqual(res.result.protocolVersion, '2024-11-05');
|
|
92
|
+
});
|
|
93
|
+
await t.test('should list tools', async () => {
|
|
94
|
+
const res = await sendRequest({
|
|
95
|
+
jsonrpc: '2.0',
|
|
96
|
+
id: 2,
|
|
97
|
+
method: 'tools/list'
|
|
98
|
+
});
|
|
99
|
+
assert.strictEqual(res.id, 2);
|
|
100
|
+
assert.ok(res.result);
|
|
101
|
+
assert.ok(Array.isArray(res.result.tools));
|
|
102
|
+
const tools = res.result.tools.map((t) => t.name);
|
|
103
|
+
assert.ok(tools.includes('ds_list_entities'));
|
|
104
|
+
assert.ok(tools.includes('ds_check_architecture'));
|
|
105
|
+
});
|
|
106
|
+
await t.test('should call ds_list_entities', async () => {
|
|
107
|
+
const res = await sendRequest({
|
|
108
|
+
jsonrpc: '2.0',
|
|
109
|
+
id: 3,
|
|
110
|
+
method: 'tools/call',
|
|
111
|
+
params: {
|
|
112
|
+
name: 'ds_list_entities'
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
assert.strictEqual(res.id, 3);
|
|
116
|
+
assert.ok(res.result);
|
|
117
|
+
assert.ok(Array.isArray(res.result.content));
|
|
118
|
+
assert.strictEqual(res.result.content[0].type, 'text');
|
|
119
|
+
const entities = JSON.parse(res.result.content[0].text);
|
|
120
|
+
assert.ok(Array.isArray(entities));
|
|
121
|
+
assert.ok(entities.some((e) => e.name === 'cabin'), 'Should include cabin');
|
|
122
|
+
});
|
|
123
|
+
await t.test('should call ds_generate_crud', async () => {
|
|
124
|
+
const res = await sendRequest({
|
|
125
|
+
jsonrpc: '2.0',
|
|
126
|
+
id: 4,
|
|
127
|
+
method: 'tools/call',
|
|
128
|
+
params: {
|
|
129
|
+
name: 'ds_generate_crud',
|
|
130
|
+
arguments: {
|
|
131
|
+
entityName: 'cabin'
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
assert.strictEqual(res.id, 4);
|
|
136
|
+
assert.ok(res.result);
|
|
137
|
+
assert.ok(Array.isArray(res.result.content));
|
|
138
|
+
assert.strictEqual(res.result.content[0].type, 'text');
|
|
139
|
+
assert.ok(res.result.content[0].text.includes('Scaffolding de CRUD completo para "cabin" finalizado!'));
|
|
140
|
+
});
|
|
141
|
+
await t.test('should call ds_check_architecture', async () => {
|
|
142
|
+
const res = await sendRequest({
|
|
143
|
+
jsonrpc: '2.0',
|
|
144
|
+
id: 5,
|
|
145
|
+
method: 'tools/call',
|
|
146
|
+
params: {
|
|
147
|
+
name: 'ds_check_architecture'
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
assert.strictEqual(res.id, 5);
|
|
151
|
+
assert.ok(res.result);
|
|
152
|
+
assert.strictEqual(res.result.isError, false);
|
|
153
|
+
assert.ok(res.result.content[0].text.includes('Varredura finalizada'));
|
|
154
|
+
});
|
|
155
|
+
// Cleanup
|
|
156
|
+
cp.stdin.end();
|
|
157
|
+
cp.kill();
|
|
158
|
+
await onData;
|
|
159
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devstroupe/devkit-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "DevsTroupe Development Kit MCP Server — exposes CLI automation as native AI tools via Model Context Protocol",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"devstroupe",
|
|
8
|
+
"devkit",
|
|
9
|
+
"mcp",
|
|
10
|
+
"model-context-protocol",
|
|
11
|
+
"ai",
|
|
12
|
+
"llm",
|
|
13
|
+
"agent",
|
|
14
|
+
"tools",
|
|
15
|
+
"nestjs",
|
|
16
|
+
"angular"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://github.com/devstroupe/devkit#readme",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/devstroupe/devkit.git",
|
|
22
|
+
"directory": "packages/devkit-mcp"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"main": "dist/index.js",
|
|
32
|
+
"bin": {
|
|
33
|
+
"devstroupe-mcp": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@devstroupe/devkit-cli": "^1.0.0",
|
|
37
|
+
"fs-extra": "^11.2.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/fs-extra": "^11.0.4",
|
|
41
|
+
"@types/node": "^20.11.0",
|
|
42
|
+
"typescript": "^5.4.5"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
46
|
+
"test": "tsc && node --test dist/integration.test.js"
|
|
47
|
+
}
|
|
48
|
+
}
|