@adiholick/nvstudio-mcp 2.1.2 → 2.1.3

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
@@ -28,7 +28,7 @@ const isDaemon = process.argv.includes('--daemon') || process.argv.includes('-d'
28
28
  // 2. Inisialisasi server MCP
29
29
  const server = new index_js_1.Server({
30
30
  name: "nvstudio-mcp",
31
- version: "2.1.0",
31
+ version: "2.1.3",
32
32
  }, {
33
33
  capabilities: {
34
34
  tools: {},
@@ -13,6 +13,13 @@ const path_1 = __importDefault(require("path"));
13
13
  const child_process_1 = require("child_process");
14
14
  const task_queue_1 = require("./task-queue");
15
15
  exports.isBridgeHosting = false;
16
+ // Global process error handlers agar server bridge tidak mati mendadak
17
+ process.on('uncaughtException', (err) => {
18
+ console.error('[Bridge] Uncaught Exception:', err);
19
+ });
20
+ process.on('unhandledRejection', (reason) => {
21
+ console.error('[Bridge] Unhandled Rejection:', reason);
22
+ });
16
23
  const activityLogs = [];
17
24
  let totalTasks = 0;
18
25
  let successCount = 0;
@@ -70,7 +77,7 @@ function broadcastStatus() {
70
77
  const statusEvent = {
71
78
  mcpConnected,
72
79
  studioCount: ssePeers.size,
73
- serverVersion: '2.1.0',
80
+ serverVersion: '2.1.3',
74
81
  };
75
82
  broadcastEvent('status', statusEvent);
76
83
  // Juga enqueue untuk Studio yang belum polling saat ini
@@ -161,10 +168,11 @@ function startBridgeServer(port = 3055) {
161
168
  });
162
169
  // ── /api/stats ─────────────────────────────────────────────────────────────
163
170
  app.get('/api/stats', (req, res) => {
171
+ const activeStudios = Math.max(ssePeers.size, studioEventQueues.size);
164
172
  res.json({
165
173
  totalTasks, successCount, errorCount, sessionStart,
166
- studioConnected: ssePeers.size > 0,
167
- studioCount: ssePeers.size,
174
+ studioConnected: activeStudios > 0,
175
+ studioCount: activeStudios,
168
176
  mcpConnected,
169
177
  port,
170
178
  });
@@ -172,7 +180,8 @@ function startBridgeServer(port = 3055) {
172
180
  // ── /api/ping ─────────────────────────────────────────────────────────────
173
181
  // Dipertahankan untuk backward-compat (installer check, health check).
174
182
  app.get('/api/ping', (req, res) => {
175
- res.json({ status: 'ok', studioCount: ssePeers.size, mcpConnected, timestamp: Date.now() });
183
+ const activeStudios = Math.max(ssePeers.size, studioEventQueues.size);
184
+ res.json({ status: 'ok', studioCount: activeStudios, mcpConnected, timestamp: Date.now() });
176
185
  });
177
186
  // ── /api/stream (Snapshot endpoint untuk Roblox GetAsync) ───────────────
178
187
  // Roblox HttpService:GetAsync() TIDAK bisa streaming — ia block sampai
@@ -204,7 +213,7 @@ function startBridgeServer(port = 3055) {
204
213
  studioEventQueues.set(studioId, []); // kosongkan antrian
205
214
  // Selalu sertakan status terkini
206
215
  const events = [
207
- { kind: 'status', mcpConnected, studioCount: studioEventQueues.size, serverVersion: '2.1.0' },
216
+ { kind: 'status', mcpConnected, studioCount: studioEventQueues.size, serverVersion: '2.1.3' },
208
217
  ];
209
218
  // Tambahkan events tertunda (misalnya task requests)
210
219
  for (const ev of queue) {
@@ -253,7 +262,7 @@ function startBridgeServer(port = 3055) {
253
262
  const silenceTimer = setTimeout(() => cleanup(), 60000);
254
263
  const peer = { studioId, res, connectedAt: Date.now(), heartbeatTimer, silenceTimer };
255
264
  ssePeers.set(studioId, peer);
256
- sendEvent(peer, 'status', { mcpConnected, studioCount: studioEventQueues.size, serverVersion: '2.1.0' });
265
+ sendEvent(peer, 'status', { mcpConnected, studioCount: studioEventQueues.size, serverVersion: '2.1.3' });
257
266
  function cleanup() {
258
267
  clearInterval(heartbeatTimer);
259
268
  clearTimeout(silenceTimer);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adiholick/nvstudio-mcp",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "Local Bridge Server MCP for Roblox Studio",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  -- ══════════════════════════════════════════════════════════════════════
2
- -- NVStudio MCP Plugin v2.1.0
3
- -- Arsitektur: SSE via Long-Poll (Roblox tidak punya native SSE client)
2
+ -- NVStudio MCP Plugin v2.1.3
3
+ -- Arsitektur: Polling Stream / Local HTTP Bridge (Port 3055)
4
4
  -- Plugin auto-connect saat dimuat, polling /api/ping lalu /api/stream
5
5
  -- untuk mendapatkan events (status, request) dari server Node.js.
6
6
  -- ══════════════════════════════════════════════════════════════════════
@@ -224,16 +224,12 @@ local function handleSSELine(line)
224
224
  -- status: update mcpConnected
225
225
  if kind == "status" then
226
226
  local newActive = decoded.mcpConnected == true
227
- if newActive ~= mcpAgentActive then
228
- mcpAgentActive = newActive
229
- if ui then
230
- if mcpAgentActive then
231
- ui:setStatus("connected")
232
- ui:addLog("[System] ✅ IDE Agent (Antigravity) terhubung ke MCP.", Color3.fromRGB(100, 255, 100))
233
- else
234
- ui:setStatus("waiting")
235
- ui:addLog("[System] IDE Agent terputus dari MCP. Menunggu...", Color3.fromRGB(255, 200, 100))
236
- end
227
+ mcpAgentActive = newActive
228
+ if ui then
229
+ -- Jaga status tetap Connected selama bridge server hidup
230
+ ui:setStatus("connected")
231
+ if newActive then
232
+ ui:addLog("[System] ✅ IDE Agent (Antigravity) aktif.", Color3.fromRGB(100, 255, 100))
237
233
  end
238
234
  end
239
235
  return
@@ -271,93 +267,60 @@ end
271
267
  function startConnectionLoop()
272
268
  if isRunning then return end
273
269
  isRunning = true
270
+ isManualDisconnect = false
274
271
  reconnectDelay = RECONNECT_DELAY_INITIAL
275
272
 
276
273
  if ui then
277
- ui:addLog("[System] NVStudio MCP v2.1.0 — Menghubungkan ke server...", Color3.fromRGB(200, 200, 200))
274
+ ui:addLog("[System] NVStudio MCP v2.1.3 — Menghubungkan ke server...", Color3.fromRGB(200, 200, 200))
278
275
  end
279
276
 
277
+ local consecutiveErrors = 0
278
+
280
279
  while isRunning and not isManualDisconnect do
281
- -- Langkah 1: Ping untuk cek apakah server hidup
282
- local pingOk, pingRaw = pcall(function()
280
+ -- Stream / polling snapshot dari server
281
+ local streamOk, streamRaw = pcall(function()
283
282
  return HttpService:GetAsync(
284
- PING_URL .. "?studioId=" .. HttpService:UrlEncode(STUDIO_ID) .. "&t=" .. tostring(os.clock()),
283
+ STREAM_URL .. "?studioId=" .. HttpService:UrlEncode(STUDIO_ID),
285
284
  false
286
285
  )
287
286
  end)
288
287
 
289
- if not pingOk then
290
- -- Server tidak reachable
291
- if serverReachable then
292
- serverReachable = false
293
- mcpAgentActive = false
294
- if ui then
295
- ui:setStatus("waiting")
296
- ui:addLog("[System] ⚠️ Server MCP tidak dapat dijangkau. Retry dalam " .. reconnectDelay .. "s...", Color3.fromRGB(255, 150, 50))
297
- end
298
- end
299
- task.wait(reconnectDelay)
300
- reconnectDelay = math.min(reconnectDelay * 2, RECONNECT_DELAY_MAX)
301
- else
302
- -- Server reachable → parse ping response
288
+ if streamOk and streamRaw and streamRaw ~= "" then
289
+ consecutiveErrors = 0
303
290
  reconnectDelay = RECONNECT_DELAY_INITIAL
304
291
 
305
- local pingData = nil
306
- pcall(function()
307
- pingData = HttpService:JSONDecode(pingRaw)
308
- end)
309
-
310
- local wasReachable = serverReachable
311
- serverReachable = true
312
-
313
- if not wasReachable and ui then
314
- ui:addLog("[System] ✅ Server MCP terhubung. Membuka event stream...", Color3.fromRGB(100, 255, 150))
292
+ if not serverReachable then
293
+ serverReachable = true
294
+ if ui then
295
+ ui:setStatus("connected")
296
+ ui:addLog("[System] ✅ Server MCP terhubung & siap.", Color3.fromRGB(100, 255, 150))
297
+ end
315
298
  end
316
299
 
317
- -- Update mcpConnected dari ping response
318
- if pingData then
319
- local newActive = pingData.mcpConnected == true
320
- if newActive ~= mcpAgentActive then
321
- mcpAgentActive = newActive
322
- if ui then
323
- if mcpAgentActive then
324
- ui:setStatus("connected")
325
- ui:addLog("[System] ✅ IDE Agent (Antigravity) terhubung ke MCP.", Color3.fromRGB(100, 255, 100))
326
- else
327
- ui:setStatus("server_connected")
328
- -- No need to explicitly add log for IDE Agent wait every time it connects,
329
- -- bridge connected log is enough.
330
- end
331
- end
332
- elseif serverReachable and not mcpAgentActive then
333
- -- Tidak ada perubahan tapi pastikan status benar
334
- if ui then ui:setStatus("server_connected") end
300
+ -- Parse baris-baris SSE
301
+ for line in (streamRaw .. "\n"):gmatch("([^\n]*)\n") do
302
+ line = line:gsub("\r", "")
303
+ if line ~= "" then
304
+ handleSSELine(line)
335
305
  end
336
306
  end
337
-
338
- -- Langkah 2: Buka SSE stream (blocking hingga server menutup koneksi / ~10 detik)
339
- -- Server akan mengirim events yang tertunda, heartbeat, lalu tutup.
340
- if isRunning and not isManualDisconnect then
341
- local streamOk, streamRaw = pcall(function()
342
- return HttpService:GetAsync(
343
- STREAM_URL .. "?studioId=" .. HttpService:UrlEncode(STUDIO_ID),
344
- false
345
- )
346
- end)
347
-
348
- if streamOk and streamRaw and streamRaw ~= "" then
349
- -- Parse baris-baris SSE dari response
350
- for line in (streamRaw .. "\n"):gmatch("([^\n]*)\n") do
351
- line = line:gsub("\r", "")
352
- if line ~= "" then
353
- handleSSELine(line)
354
- end
307
+ task.wait(0.8)
308
+ else
309
+ consecutiveErrors = consecutiveErrors + 1
310
+ -- Toleransi 2x gagal sebelum menandai server unreachable
311
+ if consecutiveErrors >= 2 then
312
+ if serverReachable then
313
+ serverReachable = false
314
+ mcpAgentActive = false
315
+ if ui then
316
+ ui:setStatus("waiting")
317
+ ui:addLog("[System] ⚠️ Server MCP tidak dapat dijangkau. Retry dalam " .. reconnectDelay .. "s...", Color3.fromRGB(255, 150, 50))
355
318
  end
356
319
  end
357
320
  end
358
321
 
359
- -- Jeda singkat sebelum cycle berikutnya
360
- task.wait(0.5)
322
+ task.wait(reconnectDelay)
323
+ reconnectDelay = math.min(reconnectDelay * 2, RECONNECT_DELAY_MAX)
361
324
  end
362
325
  end
363
326
 
@@ -379,8 +379,8 @@ end
379
379
  <BinaryString name="Tags"></BinaryString>
380
380
  <token name="RunContext">0</token>
381
381
  <ProtectedString name="Source"><![CDATA[-- ══════════════════════════════════════════════════════════════════════
382
- -- NVStudio MCP Plugin v2.1.0
383
- -- Arsitektur: SSE via Long-Poll (Roblox tidak punya native SSE client)
382
+ -- NVStudio MCP Plugin v2.1.3
383
+ -- Arsitektur: Polling Stream / Local HTTP Bridge (Port 3055)
384
384
  -- Plugin auto-connect saat dimuat, polling /api/ping lalu /api/stream
385
385
  -- untuk mendapatkan events (status, request) dari server Node.js.
386
386
  -- ══════════════════════════════════════════════════════════════════════
@@ -604,16 +604,12 @@ local function handleSSELine(line)
604
604
  -- status: update mcpConnected
605
605
  if kind == "status" then
606
606
  local newActive = decoded.mcpConnected == true
607
- if newActive ~= mcpAgentActive then
608
- mcpAgentActive = newActive
609
- if ui then
610
- if mcpAgentActive then
611
- ui:setStatus("connected")
612
- ui:addLog("[System] ✅ IDE Agent (Antigravity) terhubung ke MCP.", Color3.fromRGB(100, 255, 100))
613
- else
614
- ui:setStatus("waiting")
615
- ui:addLog("[System] IDE Agent terputus dari MCP. Menunggu...", Color3.fromRGB(255, 200, 100))
616
- end
607
+ mcpAgentActive = newActive
608
+ if ui then
609
+ -- Jaga status tetap Connected selama bridge server hidup
610
+ ui:setStatus("connected")
611
+ if newActive then
612
+ ui:addLog("[System] ✅ IDE Agent (Antigravity) aktif.", Color3.fromRGB(100, 255, 100))
617
613
  end
618
614
  end
619
615
  return
@@ -651,93 +647,60 @@ end
651
647
  function startConnectionLoop()
652
648
  if isRunning then return end
653
649
  isRunning = true
650
+ isManualDisconnect = false
654
651
  reconnectDelay = RECONNECT_DELAY_INITIAL
655
652
 
656
653
  if ui then
657
- ui:addLog("[System] NVStudio MCP v2.1.0 — Menghubungkan ke server...", Color3.fromRGB(200, 200, 200))
654
+ ui:addLog("[System] NVStudio MCP v2.1.3 — Menghubungkan ke server...", Color3.fromRGB(200, 200, 200))
658
655
  end
659
656
 
657
+ local consecutiveErrors = 0
658
+
660
659
  while isRunning and not isManualDisconnect do
661
- -- Langkah 1: Ping untuk cek apakah server hidup
662
- local pingOk, pingRaw = pcall(function()
660
+ -- Stream / polling snapshot dari server
661
+ local streamOk, streamRaw = pcall(function()
663
662
  return HttpService:GetAsync(
664
- PING_URL .. "?studioId=" .. HttpService:UrlEncode(STUDIO_ID) .. "&t=" .. tostring(os.clock()),
663
+ STREAM_URL .. "?studioId=" .. HttpService:UrlEncode(STUDIO_ID),
665
664
  false
666
665
  )
667
666
  end)
668
667
 
669
- if not pingOk then
670
- -- Server tidak reachable
671
- if serverReachable then
672
- serverReachable = false
673
- mcpAgentActive = false
674
- if ui then
675
- ui:setStatus("waiting")
676
- ui:addLog("[System] ⚠️ Server MCP tidak dapat dijangkau. Retry dalam " .. reconnectDelay .. "s...", Color3.fromRGB(255, 150, 50))
677
- end
678
- end
679
- task.wait(reconnectDelay)
680
- reconnectDelay = math.min(reconnectDelay * 2, RECONNECT_DELAY_MAX)
681
- else
682
- -- Server reachable → parse ping response
668
+ if streamOk and streamRaw and streamRaw ~= "" then
669
+ consecutiveErrors = 0
683
670
  reconnectDelay = RECONNECT_DELAY_INITIAL
684
671
 
685
- local pingData = nil
686
- pcall(function()
687
- pingData = HttpService:JSONDecode(pingRaw)
688
- end)
689
-
690
- local wasReachable = serverReachable
691
- serverReachable = true
692
-
693
- if not wasReachable and ui then
694
- ui:addLog("[System] ✅ Server MCP terhubung. Membuka event stream...", Color3.fromRGB(100, 255, 150))
672
+ if not serverReachable then
673
+ serverReachable = true
674
+ if ui then
675
+ ui:setStatus("connected")
676
+ ui:addLog("[System] ✅ Server MCP terhubung & siap.", Color3.fromRGB(100, 255, 150))
677
+ end
695
678
  end
696
679
 
697
- -- Update mcpConnected dari ping response
698
- if pingData then
699
- local newActive = pingData.mcpConnected == true
700
- if newActive ~= mcpAgentActive then
701
- mcpAgentActive = newActive
702
- if ui then
703
- if mcpAgentActive then
704
- ui:setStatus("connected")
705
- ui:addLog("[System] ✅ IDE Agent (Antigravity) terhubung ke MCP.", Color3.fromRGB(100, 255, 100))
706
- else
707
- ui:setStatus("server_connected")
708
- -- No need to explicitly add log for IDE Agent wait every time it connects,
709
- -- bridge connected log is enough.
710
- end
711
- end
712
- elseif serverReachable and not mcpAgentActive then
713
- -- Tidak ada perubahan tapi pastikan status benar
714
- if ui then ui:setStatus("server_connected") end
680
+ -- Parse baris-baris SSE
681
+ for line in (streamRaw .. "\n"):gmatch("([^\n]*)\n") do
682
+ line = line:gsub("\r", "")
683
+ if line ~= "" then
684
+ handleSSELine(line)
715
685
  end
716
686
  end
717
-
718
- -- Langkah 2: Buka SSE stream (blocking hingga server menutup koneksi / ~10 detik)
719
- -- Server akan mengirim events yang tertunda, heartbeat, lalu tutup.
720
- if isRunning and not isManualDisconnect then
721
- local streamOk, streamRaw = pcall(function()
722
- return HttpService:GetAsync(
723
- STREAM_URL .. "?studioId=" .. HttpService:UrlEncode(STUDIO_ID),
724
- false
725
- )
726
- end)
727
-
728
- if streamOk and streamRaw and streamRaw ~= "" then
729
- -- Parse baris-baris SSE dari response
730
- for line in (streamRaw .. "\n"):gmatch("([^\n]*)\n") do
731
- line = line:gsub("\r", "")
732
- if line ~= "" then
733
- handleSSELine(line)
734
- end
687
+ task.wait(0.8)
688
+ else
689
+ consecutiveErrors = consecutiveErrors + 1
690
+ -- Toleransi 2x gagal sebelum menandai server unreachable
691
+ if consecutiveErrors >= 2 then
692
+ if serverReachable then
693
+ serverReachable = false
694
+ mcpAgentActive = false
695
+ if ui then
696
+ ui:setStatus("waiting")
697
+ ui:addLog("[System] ⚠️ Server MCP tidak dapat dijangkau. Retry dalam " .. reconnectDelay .. "s...", Color3.fromRGB(255, 150, 50))
735
698
  end
736
699
  end
737
700
  end
738
701
 
739
- -- Jeda singkat sebelum cycle berikutnya
740
- task.wait(0.5)
702
+ task.wait(reconnectDelay)
703
+ reconnectDelay = math.min(reconnectDelay * 2, RECONNECT_DELAY_MAX)
741
704
  end
742
705
  end
743
706
 
@@ -1115,7 +1078,7 @@ function UIManager.init(pluginInstance, studioId)
1115
1078
 
1116
1079
  -- Version
1117
1080
  local versionLabel = Instance.new("TextLabel")
1118
- versionLabel.Text = "v2.1.2"
1081
+ versionLabel.Text = "v2.1.3"
1119
1082
  versionLabel.Font = Enum.Font.Gotham
1120
1083
  versionLabel.TextSize = 11
1121
1084
  versionLabel.TextColor3 = Color3.fromRGB(120, 120, 130)
@@ -95,7 +95,7 @@ function UIManager.init(pluginInstance, studioId)
95
95
 
96
96
  -- Version
97
97
  local versionLabel = Instance.new("TextLabel")
98
- versionLabel.Text = "v2.1.2"
98
+ versionLabel.Text = "v2.1.3"
99
99
  versionLabel.Font = Enum.Font.Gotham
100
100
  versionLabel.TextSize = 11
101
101
  versionLabel.TextColor3 = Color3.fromRGB(120, 120, 130)