@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 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
- // Fallback: Cek apakah parent process (IDE) masih hidup secara berkala
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
- try {
143
- // process.kill dengan signal 0 hanya mengecek keberadaan proses tanpa membunuhnya
144
- process.kill(process.ppid, 0);
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
  }
@@ -22,17 +22,58 @@ function addLog(io, type, message) {
22
22
  type,
23
23
  message,
24
24
  };
25
- activityLogs.unshift(log); // newest first
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
- httpServer.listen(port, () => {
93
- console.error(`[Bridge] Dashboard visual & server aktif di http://localhost:${port}`);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adiholick/nvstudio-mcp",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Local Bridge Server MCP for Roblox Studio",
5
5
  "main": "dist/index.js",
6
6
  "bin": "dist/index.js",