@devstroupe/devkit-mcp 1.1.16 → 1.2.0-beta.1

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/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ export declare function dispatchRequest(request: any): Promise<any>;
package/dist/index.js CHANGED
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  };
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.dispatchRequest = dispatchRequest;
37
38
  const path = __importStar(require("path"));
38
39
  const fs = __importStar(require("fs-extra"));
39
40
  const readline = __importStar(require("readline"));
@@ -47,11 +48,6 @@ try {
47
48
  catch (e) {
48
49
  // Ignora se não houver ts-node
49
50
  }
50
- const rl = readline.createInterface({
51
- input: process.stdin,
52
- output: process.stdout,
53
- terminal: false
54
- });
55
51
  const SPARTAN_STYLES = ['nova', 'vega', 'lyra', 'maia', 'mira', 'luma'];
56
52
  /**
57
53
  * Resolve o caminho do CLI do devstroupe.
@@ -68,28 +64,50 @@ function resolveCliPath() {
68
64
  return path.resolve(__dirname, '../../devkit-cli/dist/index.js');
69
65
  }
70
66
  }
71
- function sendResponse(response) {
67
+ let responseSink = (response) => {
72
68
  process.stdout.write(JSON.stringify(response) + '\n');
73
- }
69
+ };
70
+ function sendResponse(response) { responseSink(response); }
74
71
  function sendError(id, code, message) {
75
72
  sendResponse({ jsonrpc: '2.0', id, error: { code, message } });
76
73
  }
77
74
  function sendResult(id, result) {
78
75
  sendResponse({ jsonrpc: '2.0', id, result });
79
76
  }
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
- });
77
+ function dispatchRequest(request) {
78
+ return new Promise((resolve, reject) => {
79
+ const previousSink = responseSink;
80
+ responseSink = (response) => {
81
+ responseSink = previousSink;
82
+ resolve(response);
83
+ };
84
+ try {
85
+ if (request?.id === undefined)
86
+ throw new Error('Test dispatch requires a request id.');
87
+ handleRequest(request);
88
+ }
89
+ catch (error) {
90
+ responseSink = previousSink;
91
+ reject(error);
92
+ }
93
+ });
94
+ }
95
+ function startStdioServer() {
96
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
97
+ rl.on('line', (line) => {
98
+ if (!line.trim())
99
+ return;
100
+ try {
101
+ const request = JSON.parse(line);
102
+ if (request.id === undefined)
103
+ return;
104
+ handleRequest(request);
105
+ }
106
+ catch {
107
+ sendError(null, -32700, 'Parse error');
108
+ }
109
+ });
110
+ }
93
111
  function handleRequest(request) {
94
112
  const { id, method, params } = request;
95
113
  switch (method) {
@@ -351,3 +369,6 @@ function handleToolCall(id, toolName, args) {
351
369
  sendError(id, -32601, `Ferramenta não encontrada: ${toolName}`);
352
370
  }
353
371
  }
372
+ if (require.main === module) {
373
+ startStdioServer();
374
+ }
@@ -1,204 +1,55 @@
1
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
- })();
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
35
5
  Object.defineProperty(exports, "__esModule", { value: true });
36
6
  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
- const fs = __importStar(require("fs-extra"));
41
- (0, node_test_1.test)('MCP Server Integration Test', async (t) => {
42
- const serverPath = path.resolve(__dirname, 'index.js');
43
- const sandboxDir = path.resolve(__dirname, '../../../sandbox-test');
44
- // Limpar e inicializar o sandbox-test dinamicamente
45
- try {
46
- fs.removeSync(sandboxDir);
47
- }
48
- catch { }
49
- fs.ensureDirSync(sandboxDir);
50
- const rootDir = path.resolve(__dirname, '../../..');
51
- const angularTemplate = path.join(rootDir, 'boilerplates/angular-template');
52
- const nestTemplate = path.join(rootDir, 'boilerplates/nest-template');
53
- const filter = (src) => !src.includes('node_modules') && !src.includes('.angular') && !src.includes('.nest');
54
- fs.copySync(angularTemplate, path.join(sandboxDir, 'frontend'), { filter });
55
- fs.copySync(nestTemplate, path.join(sandboxDir, 'backend'), { filter });
56
- const destAngularNodeModules = path.join(sandboxDir, 'frontend/node_modules');
57
- const destNestNodeModules = path.join(sandboxDir, 'backend/node_modules');
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const node_os_1 = __importDefault(require("node:os"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const index_1 = require("./index");
12
+ (0, node_test_1.test)('MCP server dispatches protocol and config tools without a subprocess', async (t) => {
13
+ const sandbox = fs_extra_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'devkit-mcp-'));
14
+ fs_extra_1.default.writeFileSync(node_path_1.default.join(sandbox, 'devstroupe.config.ts'), `module.exports = {
15
+ projectName: 'MCP fixture',
16
+ backendPath: './backend',
17
+ frontendPath: './frontend',
18
+ entities: [{ name: 'cabin', module: 'booking', properties: [] }]
19
+ };\n`);
20
+ const previous = process.cwd();
21
+ process.chdir(sandbox);
58
22
  try {
59
- fs.removeSync(destAngularNodeModules);
60
- }
61
- catch { }
62
- try {
63
- fs.removeSync(destNestNodeModules);
64
- }
65
- catch { }
66
- fs.ensureSymlinkSync(path.join(angularTemplate, 'node_modules'), destAngularNodeModules);
67
- fs.ensureSymlinkSync(path.join(nestTemplate, 'node_modules'), destNestNodeModules);
68
- fs.writeFileSync(path.join(sandboxDir, 'devstroupe.config.ts'), `export default {
69
- projectName: 'Sandbox Test',
70
- backendPath: './backend',
71
- frontendPath: './frontend',
72
- entities: [
73
- {
74
- name: 'cabin',
75
- label: 'Cabines',
76
- tableName: 'cabins',
77
- properties: [
78
- { name: 'name', type: 'string', required: true, label: 'Nome' }
79
- ]
80
- }
81
- ]
82
- };`, 'utf-8');
83
- const cp = (0, child_process_1.spawn)('node', [serverPath], {
84
- cwd: sandboxDir,
85
- stdio: ['pipe', 'pipe', 'inherit']
86
- });
87
- const responses = [];
88
- let buffer = '';
89
- const onData = new Promise((resolve) => {
90
- cp.stdout.on('data', (chunk) => {
91
- buffer += chunk.toString();
92
- let index;
93
- while ((index = buffer.indexOf('\n')) !== -1) {
94
- const line = buffer.substring(0, index).trim();
95
- buffer = buffer.substring(index + 1);
96
- if (line) {
97
- responses.push(line);
98
- }
99
- }
100
- });
101
- cp.on('close', () => resolve());
102
- });
103
- async function sendRequest(req) {
104
- cp.stdin.write(JSON.stringify(req) + '\n');
105
- const startTime = Date.now();
106
- while (Date.now() - startTime < 10000) { // 10s timeout
107
- for (let i = 0; i < responses.length; i++) {
108
- try {
109
- const res = JSON.parse(responses[i]);
110
- if (res.id === req.id) {
111
- responses.splice(i, 1);
112
- return res;
113
- }
114
- }
115
- catch { }
116
- }
117
- await new Promise((r) => setTimeout(r, 100));
118
- }
119
- throw new Error(`Timeout waiting for response to request id ${req.id}`);
120
- }
121
- await t.test('should respond to initialize', async () => {
122
- const res = await sendRequest({
123
- jsonrpc: '2.0',
124
- id: 1,
125
- method: 'initialize',
126
- params: { protocolVersion: '2024-11-05' }
23
+ await t.test('initialize', async () => {
24
+ const response = await (0, index_1.dispatchRequest)({
25
+ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05' },
26
+ });
27
+ strict_1.default.equal(response.id, 1);
28
+ strict_1.default.equal(response.result.protocolVersion, '2024-11-05');
29
+ strict_1.default.equal(response.result.serverInfo.name, 'devkit-mcp');
127
30
  });
128
- assert.strictEqual(res.jsonrpc, '2.0');
129
- assert.strictEqual(res.id, 1);
130
- assert.ok(res.result);
131
- assert.strictEqual(res.result.protocolVersion, '2024-11-05');
132
- });
133
- await t.test('should list tools', async () => {
134
- const res = await sendRequest({
135
- jsonrpc: '2.0',
136
- id: 2,
137
- method: 'tools/list'
31
+ await t.test('tools/list exposes generation and governance', async () => {
32
+ const response = await (0, index_1.dispatchRequest)({ jsonrpc: '2.0', id: 2, method: 'tools/list' });
33
+ const names = response.result.tools.map((tool) => tool.name);
34
+ strict_1.default.ok(names.includes('ds_list_entities'));
35
+ strict_1.default.ok(names.includes('ds_generate_crud'));
36
+ strict_1.default.ok(names.includes('ds_check_architecture'));
37
+ strict_1.default.ok(names.includes('ds_run_harnesses'));
138
38
  });
139
- assert.strictEqual(res.id, 2);
140
- assert.ok(res.result);
141
- assert.ok(Array.isArray(res.result.tools));
142
- const tools = res.result.tools.map((t) => t.name);
143
- assert.ok(tools.includes('ds_list_entities'));
144
- assert.ok(tools.includes('ds_check_architecture'));
145
- });
146
- await t.test('should call ds_list_entities', async () => {
147
- const res = await sendRequest({
148
- jsonrpc: '2.0',
149
- id: 3,
150
- method: 'tools/call',
151
- params: {
152
- name: 'ds_list_entities'
153
- }
39
+ await t.test('ds_list_entities reads the active project config', async () => {
40
+ const response = await (0, index_1.dispatchRequest)({
41
+ jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name: 'ds_list_entities' },
42
+ });
43
+ const entities = JSON.parse(response.result.content[0].text);
44
+ strict_1.default.deepEqual(entities, [{ name: 'cabin', module: 'booking', properties: [] }]);
154
45
  });
155
- assert.strictEqual(res.id, 3);
156
- assert.ok(res.result);
157
- assert.ok(Array.isArray(res.result.content));
158
- assert.strictEqual(res.result.content[0].type, 'text');
159
- const entities = JSON.parse(res.result.content[0].text);
160
- assert.ok(Array.isArray(entities));
161
- assert.ok(entities.some((e) => e.name === 'cabin'), 'Should include cabin');
162
- });
163
- await t.test('should call ds_generate_crud', async () => {
164
- const res = await sendRequest({
165
- jsonrpc: '2.0',
166
- id: 4,
167
- method: 'tools/call',
168
- params: {
169
- name: 'ds_generate_crud',
170
- arguments: {
171
- entityName: 'cabin'
172
- }
173
- }
46
+ await t.test('unknown methods return JSON-RPC errors', async () => {
47
+ const response = await (0, index_1.dispatchRequest)({ jsonrpc: '2.0', id: 4, method: 'unknown' });
48
+ strict_1.default.equal(response.error.code, -32601);
174
49
  });
175
- assert.strictEqual(res.id, 4);
176
- assert.ok(res.result);
177
- assert.ok(Array.isArray(res.result.content));
178
- assert.strictEqual(res.result.content[0].type, 'text');
179
- assert.ok(res.result.content[0].text.includes('Scaffolding de CRUD completo para "cabin" finalizado!'));
180
- });
181
- await t.test('should call ds_check_architecture', async () => {
182
- const res = await sendRequest({
183
- jsonrpc: '2.0',
184
- id: 5,
185
- method: 'tools/call',
186
- params: {
187
- name: 'ds_check_architecture'
188
- }
189
- });
190
- assert.strictEqual(res.id, 5);
191
- assert.ok(res.result);
192
- assert.strictEqual(res.result.isError, false);
193
- assert.ok(res.result.content[0].text.includes('Varredura finalizada'));
194
- });
195
- // Cleanup
196
- cp.stdin.end();
197
- cp.kill();
198
- await onData;
199
- // Remover sandbox-test
200
- try {
201
- fs.removeSync(sandboxDir);
202
50
  }
203
- catch { }
51
+ finally {
52
+ process.chdir(previous);
53
+ fs_extra_1.default.removeSync(sandbox);
54
+ }
204
55
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devstroupe/devkit-mcp",
3
- "version": "1.1.16",
3
+ "version": "1.2.0-beta.1",
4
4
  "description": "DevsTroupe Development Kit MCP Server — exposes CLI automation as native AI tools via Model Context Protocol",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "fs-extra": "^11.2.0",
37
- "@devstroupe/devkit-cli": "1.1.16"
37
+ "@devstroupe/devkit-cli": "1.2.0-beta.1"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/fs-extra": "^11.0.4",