@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 +1 -1
- package/dist/roblox-bridge.js +15 -6
- package/package.json +1 -1
- package/studio-plugin/Main.server.lua +41 -78
- package/studio-plugin/nvstudio_mcp.rbxmx +42 -79
- package/studio-plugin/ui_manager.lua +1 -1
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.
|
|
31
|
+
version: "2.1.3",
|
|
32
32
|
}, {
|
|
33
33
|
capabilities: {
|
|
34
34
|
tools: {},
|
package/dist/roblox-bridge.js
CHANGED
|
@@ -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.
|
|
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:
|
|
167
|
-
studioCount:
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
-- NVStudio MCP Plugin v2.1.
|
|
3
|
-
-- Arsitektur:
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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.
|
|
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
|
-
--
|
|
282
|
-
local
|
|
280
|
+
-- Stream / polling snapshot dari server
|
|
281
|
+
local streamOk, streamRaw = pcall(function()
|
|
283
282
|
return HttpService:GetAsync(
|
|
284
|
-
|
|
283
|
+
STREAM_URL .. "?studioId=" .. HttpService:UrlEncode(STUDIO_ID),
|
|
285
284
|
false
|
|
286
285
|
)
|
|
287
286
|
end)
|
|
288
287
|
|
|
289
|
-
if
|
|
290
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
--
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
if
|
|
321
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
360
|
-
|
|
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.
|
|
383
|
-
-- Arsitektur:
|
|
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
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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.
|
|
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
|
-
--
|
|
662
|
-
local
|
|
660
|
+
-- Stream / polling snapshot dari server
|
|
661
|
+
local streamOk, streamRaw = pcall(function()
|
|
663
662
|
return HttpService:GetAsync(
|
|
664
|
-
|
|
663
|
+
STREAM_URL .. "?studioId=" .. HttpService:UrlEncode(STUDIO_ID),
|
|
665
664
|
false
|
|
666
665
|
)
|
|
667
666
|
end)
|
|
668
667
|
|
|
669
|
-
if
|
|
670
|
-
|
|
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
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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
|
-
--
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
if
|
|
701
|
-
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
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
|
-
|
|
740
|
-
|
|
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.
|
|
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.
|
|
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)
|