@adiholick/nvstudio-mcp 1.0.3 → 1.0.4
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.js +11 -9
- package/dist/roblox-bridge.js +64 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -130,22 +130,24 @@ async function main() {
|
|
|
130
130
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
131
131
|
await server.connect(transport);
|
|
132
132
|
console.error("[nvstudio-mcp] MCP Server aktif melalui StdioServerTransport.");
|
|
133
|
+
let isShuttingDown = false;
|
|
133
134
|
const shutdown = () => {
|
|
135
|
+
if (isShuttingDown)
|
|
136
|
+
return;
|
|
137
|
+
isShuttingDown = true;
|
|
134
138
|
console.error("[nvstudio-mcp] Koneksi ditutup oleh IDE. Mematikan server...");
|
|
135
139
|
process.exit(0);
|
|
136
140
|
};
|
|
137
|
-
// Tangani penutupan koneksi
|
|
141
|
+
// Tangani penutupan koneksi stdin (ketika IDE menutup pipe)
|
|
138
142
|
process.stdin.on('close', shutdown);
|
|
139
143
|
process.stdin.on('end', shutdown);
|
|
140
|
-
|
|
144
|
+
process.stdin.on('error', shutdown);
|
|
145
|
+
// Fallback khusus Windows: cek apakah stdin masih readable secara berkala
|
|
146
|
+
// process.kill(ppid, 0) tidak reliable di Windows, jadi kita pakai cara ini
|
|
141
147
|
setInterval(() => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
catch (e) {
|
|
147
|
-
console.error("[nvstudio-mcp] Parent process (IDE) sudah mati. Mematikan server untuk menghindari zombie connection...");
|
|
148
|
-
process.exit(0);
|
|
148
|
+
if (!process.stdin.readable) {
|
|
149
|
+
console.error("[nvstudio-mcp] stdin tidak readable lagi. IDE kemungkinan sudah ditutup.");
|
|
150
|
+
shutdown();
|
|
149
151
|
}
|
|
150
152
|
}, 3000);
|
|
151
153
|
}
|
package/dist/roblox-bridge.js
CHANGED
|
@@ -22,17 +22,58 @@ function addLog(io, type, message) {
|
|
|
22
22
|
type,
|
|
23
23
|
message,
|
|
24
24
|
};
|
|
25
|
-
activityLogs.unshift(log);
|
|
25
|
+
activityLogs.unshift(log);
|
|
26
26
|
if (activityLogs.length > 200)
|
|
27
27
|
activityLogs.pop();
|
|
28
28
|
io.emit('log', log);
|
|
29
29
|
io.emit('stats', { totalTasks, successCount, errorCount, sessionStart });
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Membunuh proses zombie yang masih menyandera port tertentu.
|
|
33
|
+
* Ini menyelesaikan masalah EADDRINUSE yang terjadi setelah restart IDE.
|
|
34
|
+
*/
|
|
35
|
+
function killZombieOnPort(port) {
|
|
36
|
+
try {
|
|
37
|
+
if (process.platform === 'win32') {
|
|
38
|
+
const output = (0, child_process_1.execSync)(`netstat -ano | findstr :${port} | findstr LISTENING`, { encoding: 'utf8', timeout: 5000 });
|
|
39
|
+
const lines = output.trim().split('\n');
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
const parts = line.trim().split(/\s+/);
|
|
42
|
+
const pid = parts[parts.length - 1];
|
|
43
|
+
if (pid && pid !== '0' && pid !== String(process.pid)) {
|
|
44
|
+
try {
|
|
45
|
+
(0, child_process_1.execSync)(`taskkill /F /PID ${pid}`, { encoding: 'utf8', timeout: 5000 });
|
|
46
|
+
console.error(`[Bridge] Zombie process (PID ${pid}) pada port ${port} berhasil dihentikan.`);
|
|
47
|
+
}
|
|
48
|
+
catch { /* ignore if already dead */ }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// macOS / Linux
|
|
55
|
+
const output = (0, child_process_1.execSync)(`lsof -ti :${port}`, { encoding: 'utf8', timeout: 5000 });
|
|
56
|
+
const pids = output.trim().split('\n');
|
|
57
|
+
for (const pid of pids) {
|
|
58
|
+
if (pid && pid !== String(process.pid)) {
|
|
59
|
+
try {
|
|
60
|
+
(0, child_process_1.execSync)(`kill -9 ${pid}`);
|
|
61
|
+
}
|
|
62
|
+
catch { /* ignore */ }
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Tidak ada proses yang menyandera port — ini normal
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
31
73
|
function startBridgeServer(port = 3055) {
|
|
32
74
|
const app = (0, express_1.default)();
|
|
33
75
|
const httpServer = (0, http_1.createServer)(app);
|
|
34
76
|
const io = new socket_io_1.Server(httpServer, { cors: { origin: "*" } });
|
|
35
|
-
// FIX: Menaikkan limit ke 50mb agar file .Source (Lua) yang besar tidak melempar error 413 Payload Too Large
|
|
36
77
|
app.use(express_1.default.json({ limit: '50mb' }));
|
|
37
78
|
app.use(express_1.default.static(path_1.default.join(__dirname, '../public')));
|
|
38
79
|
let dashboardOpened = false;
|
|
@@ -89,7 +130,25 @@ function startBridgeServer(port = 3055) {
|
|
|
89
130
|
(0, task_queue_1.resolvePendingTask)(id, status, result, error);
|
|
90
131
|
res.json({ message: "Response diterima" });
|
|
91
132
|
});
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
133
|
+
// ── ANTI-ZOMBIE: Coba listen, jika gagal bunuh zombie lalu retry ──
|
|
134
|
+
function tryListen(retriesLeft = 2) {
|
|
135
|
+
const server = httpServer.listen(port, () => {
|
|
136
|
+
console.error(`[Bridge] Dashboard visual & server aktif di http://localhost:${port}`);
|
|
137
|
+
});
|
|
138
|
+
server.on('error', (err) => {
|
|
139
|
+
if (err.code === 'EADDRINUSE' && retriesLeft > 0) {
|
|
140
|
+
console.error(`[Bridge] Port ${port} sedang digunakan oleh proses zombie. Mencoba membunuh...`);
|
|
141
|
+
killZombieOnPort(port);
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
console.error(`[Bridge] Mencoba listen ulang pada port ${port}... (sisa ${retriesLeft - 1} percobaan)`);
|
|
144
|
+
tryListen(retriesLeft - 1);
|
|
145
|
+
}, 1500);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
console.error(`[Bridge] FATAL: Gagal mendengarkan port ${port}:`, err.message);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
tryListen();
|
|
95
154
|
}
|