@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 ADDED
@@ -0,0 +1,92 @@
1
+ # @devstroupe/devkit-mcp
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@devstroupe/devkit-mcp?color=purple)](https://www.npmjs.com/package/@devstroupe/devkit-mcp)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](../../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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
+ }