@devstroupe/devkit-mcp 1.1.15 → 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 +1 -1
- package/dist/index.js +41 -20
- package/dist/integration.test.js +45 -194
- package/package.json +2 -2
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
+
}
|
package/dist/integration.test.js
CHANGED
|
@@ -1,204 +1,55 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
37
|
+
"@devstroupe/devkit-cli": "1.2.0-beta.1"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/fs-extra": "^11.0.4",
|