robloxstudio-mcp 2.5.0 → 2.5.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.js +126 -19
- package/package.json +2 -3
- package/studio-plugin/MCPPlugin.rbxmx +1 -1
- package/dist/install-plugin-cli.js +0 -87
package/dist/index.js
CHANGED
|
@@ -1,4 +1,103 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/install-plugin.ts
|
|
13
|
+
var install_plugin_exports = {};
|
|
14
|
+
__export(install_plugin_exports, {
|
|
15
|
+
installPlugin: () => installPlugin
|
|
16
|
+
});
|
|
17
|
+
import { createWriteStream, existsSync as existsSync2, mkdirSync as mkdirSync2, unlinkSync } from "fs";
|
|
18
|
+
import { join as join2 } from "path";
|
|
19
|
+
import { homedir } from "os";
|
|
20
|
+
import { get } from "https";
|
|
21
|
+
function getPluginsFolder() {
|
|
22
|
+
if (process.platform === "win32") {
|
|
23
|
+
return join2(process.env.LOCALAPPDATA || join2(homedir(), "AppData", "Local"), "Roblox", "Plugins");
|
|
24
|
+
}
|
|
25
|
+
return join2(homedir(), "Documents", "Roblox", "Plugins");
|
|
26
|
+
}
|
|
27
|
+
function httpsGet(url) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const req = get(url, { headers: { "User-Agent": "robloxstudio-mcp" } }, resolve);
|
|
30
|
+
req.on("error", reject);
|
|
31
|
+
req.setTimeout(TIMEOUT_MS, () => {
|
|
32
|
+
req.destroy(new Error(`Request timed out after ${TIMEOUT_MS}ms`));
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
async function download(url, dest, redirects = 0) {
|
|
37
|
+
const res = await httpsGet(url);
|
|
38
|
+
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
39
|
+
if (redirects >= MAX_REDIRECTS) throw new Error(`Too many redirects (max ${MAX_REDIRECTS})`);
|
|
40
|
+
const location = res.headers.location;
|
|
41
|
+
if (!location) throw new Error("Redirect with no location header");
|
|
42
|
+
return download(location, dest, redirects + 1);
|
|
43
|
+
}
|
|
44
|
+
if (res.statusCode !== 200) {
|
|
45
|
+
throw new Error(`Download failed: HTTP ${res.statusCode}`);
|
|
46
|
+
}
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const file = createWriteStream(dest);
|
|
49
|
+
const cleanup = (err) => {
|
|
50
|
+
file.close(() => {
|
|
51
|
+
try {
|
|
52
|
+
unlinkSync(dest);
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
reject(err);
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
res.pipe(file);
|
|
59
|
+
file.on("finish", () => {
|
|
60
|
+
file.close();
|
|
61
|
+
resolve();
|
|
62
|
+
});
|
|
63
|
+
file.on("error", cleanup);
|
|
64
|
+
res.on("error", cleanup);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async function installPlugin() {
|
|
68
|
+
const pluginsFolder = getPluginsFolder();
|
|
69
|
+
if (!existsSync2(pluginsFolder)) {
|
|
70
|
+
mkdirSync2(pluginsFolder, { recursive: true });
|
|
71
|
+
}
|
|
72
|
+
console.log("Fetching latest release...");
|
|
73
|
+
const res = await httpsGet(`https://api.github.com/repos/${REPO}/releases/latest`);
|
|
74
|
+
if (res.statusCode !== 200) {
|
|
75
|
+
throw new Error(`GitHub API returned HTTP ${res.statusCode}`);
|
|
76
|
+
}
|
|
77
|
+
const chunks = [];
|
|
78
|
+
for await (const chunk of res) {
|
|
79
|
+
chunks.push(chunk);
|
|
80
|
+
}
|
|
81
|
+
const release = JSON.parse(Buffer.concat(chunks).toString());
|
|
82
|
+
const asset = release.assets?.find((a) => a.name === ASSET_NAME);
|
|
83
|
+
if (!asset) {
|
|
84
|
+
throw new Error(`${ASSET_NAME} not found in release ${release.tag_name}`);
|
|
85
|
+
}
|
|
86
|
+
const dest = join2(pluginsFolder, ASSET_NAME);
|
|
87
|
+
console.log(`Downloading ${ASSET_NAME} from ${release.tag_name}...`);
|
|
88
|
+
await download(asset.browser_download_url, dest);
|
|
89
|
+
console.log(`Installed to ${dest}`);
|
|
90
|
+
}
|
|
91
|
+
var REPO, ASSET_NAME, TIMEOUT_MS, MAX_REDIRECTS;
|
|
92
|
+
var init_install_plugin = __esm({
|
|
93
|
+
"src/install-plugin.ts"() {
|
|
94
|
+
"use strict";
|
|
95
|
+
REPO = "boshyxd/robloxstudio-mcp";
|
|
96
|
+
ASSET_NAME = "MCPPlugin.rbxmx";
|
|
97
|
+
TIMEOUT_MS = 3e4;
|
|
98
|
+
MAX_REDIRECTS = 5;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
2
101
|
|
|
3
102
|
// ../../node_modules/@robloxstudio-mcp/core/dist/server.js
|
|
4
103
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -223,8 +322,8 @@ function listenWithRetry(app, host, startPort, maxAttempts = 5) {
|
|
|
223
322
|
for (let i = 0; i < maxAttempts; i++) {
|
|
224
323
|
const port = startPort + i;
|
|
225
324
|
try {
|
|
226
|
-
const
|
|
227
|
-
resolve({ server
|
|
325
|
+
const server = await bindPort(app, host, port);
|
|
326
|
+
resolve({ server, port });
|
|
228
327
|
return;
|
|
229
328
|
} catch (err) {
|
|
230
329
|
if (err.code === "EADDRINUSE") {
|
|
@@ -240,15 +339,15 @@ function listenWithRetry(app, host, startPort, maxAttempts = 5) {
|
|
|
240
339
|
}
|
|
241
340
|
function bindPort(app, host, port) {
|
|
242
341
|
return new Promise((resolve, reject) => {
|
|
243
|
-
const
|
|
342
|
+
const server = http.createServer(app);
|
|
244
343
|
const onError = (err) => {
|
|
245
|
-
|
|
344
|
+
server.removeListener("error", onError);
|
|
246
345
|
reject(err);
|
|
247
346
|
};
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
resolve(
|
|
347
|
+
server.once("error", onError);
|
|
348
|
+
server.listen(port, host, () => {
|
|
349
|
+
server.removeListener("error", onError);
|
|
350
|
+
resolve(server);
|
|
252
351
|
});
|
|
253
352
|
});
|
|
254
353
|
}
|
|
@@ -3667,14 +3766,22 @@ var getAllTools = () => [...TOOL_DEFINITIONS];
|
|
|
3667
3766
|
|
|
3668
3767
|
// src/index.ts
|
|
3669
3768
|
import { createRequire } from "module";
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
}
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3769
|
+
if (process.argv.includes("--install-plugin")) {
|
|
3770
|
+
const { installPlugin: installPlugin2 } = await Promise.resolve().then(() => (init_install_plugin(), install_plugin_exports));
|
|
3771
|
+
installPlugin2().catch((err) => {
|
|
3772
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
3773
|
+
process.exitCode = 1;
|
|
3774
|
+
});
|
|
3775
|
+
} else {
|
|
3776
|
+
const require2 = createRequire(import.meta.url);
|
|
3777
|
+
const { version: VERSION } = require2("../package.json");
|
|
3778
|
+
const server = new RobloxStudioMCPServer({
|
|
3779
|
+
name: "robloxstudio-mcp",
|
|
3780
|
+
version: VERSION,
|
|
3781
|
+
tools: getAllTools()
|
|
3782
|
+
});
|
|
3783
|
+
server.run().catch((error) => {
|
|
3784
|
+
console.error("Server failed to start:", error);
|
|
3785
|
+
process.exit(1);
|
|
3786
|
+
});
|
|
3787
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "robloxstudio-mcp",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"description": "MCP Server for Roblox Studio Integration - Access Studio data, scripts, and objects through AI tools",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"robloxstudio-mcp": "dist/index.js"
|
|
9
|
-
"robloxstudio-mcp-install-plugin": "dist/install-plugin-cli.js"
|
|
8
|
+
"robloxstudio-mcp": "dist/index.js"
|
|
10
9
|
},
|
|
11
10
|
"files": [
|
|
12
11
|
"dist/**/*",
|
|
@@ -4516,7 +4516,7 @@ return {
|
|
|
4516
4516
|
<Properties>
|
|
4517
4517
|
<string name="Name">State</string>
|
|
4518
4518
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
4519
|
-
local CURRENT_VERSION = "2.5.
|
|
4519
|
+
local CURRENT_VERSION = "2.5.1"
|
|
4520
4520
|
local MAX_CONNECTIONS = 5
|
|
4521
4521
|
local BASE_PORT = 58741
|
|
4522
4522
|
local activeTabIndex = 0
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/install-plugin.ts
|
|
4
|
-
import { createWriteStream, existsSync, mkdirSync, unlinkSync } from "fs";
|
|
5
|
-
import { join } from "path";
|
|
6
|
-
import { homedir } from "os";
|
|
7
|
-
import { get } from "https";
|
|
8
|
-
var REPO = "boshyxd/robloxstudio-mcp";
|
|
9
|
-
var ASSET_NAME = "MCPPlugin.rbxmx";
|
|
10
|
-
var TIMEOUT_MS = 3e4;
|
|
11
|
-
var MAX_REDIRECTS = 5;
|
|
12
|
-
function getPluginsFolder() {
|
|
13
|
-
if (process.platform === "win32") {
|
|
14
|
-
return join(process.env.LOCALAPPDATA || join(homedir(), "AppData", "Local"), "Roblox", "Plugins");
|
|
15
|
-
}
|
|
16
|
-
return join(homedir(), "Documents", "Roblox", "Plugins");
|
|
17
|
-
}
|
|
18
|
-
function httpsGet(url) {
|
|
19
|
-
return new Promise((resolve, reject) => {
|
|
20
|
-
const req = get(url, { headers: { "User-Agent": "robloxstudio-mcp" } }, resolve);
|
|
21
|
-
req.on("error", reject);
|
|
22
|
-
req.setTimeout(TIMEOUT_MS, () => {
|
|
23
|
-
req.destroy(new Error(`Request timed out after ${TIMEOUT_MS}ms`));
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
async function download(url, dest, redirects = 0) {
|
|
28
|
-
const res = await httpsGet(url);
|
|
29
|
-
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
30
|
-
if (redirects >= MAX_REDIRECTS) throw new Error(`Too many redirects (max ${MAX_REDIRECTS})`);
|
|
31
|
-
const location = res.headers.location;
|
|
32
|
-
if (!location) throw new Error("Redirect with no location header");
|
|
33
|
-
return download(location, dest, redirects + 1);
|
|
34
|
-
}
|
|
35
|
-
if (res.statusCode !== 200) {
|
|
36
|
-
throw new Error(`Download failed: HTTP ${res.statusCode}`);
|
|
37
|
-
}
|
|
38
|
-
return new Promise((resolve, reject) => {
|
|
39
|
-
const file = createWriteStream(dest);
|
|
40
|
-
const cleanup = (err) => {
|
|
41
|
-
file.close(() => {
|
|
42
|
-
try {
|
|
43
|
-
unlinkSync(dest);
|
|
44
|
-
} catch {
|
|
45
|
-
}
|
|
46
|
-
reject(err);
|
|
47
|
-
});
|
|
48
|
-
};
|
|
49
|
-
res.pipe(file);
|
|
50
|
-
file.on("finish", () => {
|
|
51
|
-
file.close();
|
|
52
|
-
resolve();
|
|
53
|
-
});
|
|
54
|
-
file.on("error", cleanup);
|
|
55
|
-
res.on("error", cleanup);
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
async function installPlugin() {
|
|
59
|
-
const pluginsFolder = getPluginsFolder();
|
|
60
|
-
if (!existsSync(pluginsFolder)) {
|
|
61
|
-
mkdirSync(pluginsFolder, { recursive: true });
|
|
62
|
-
}
|
|
63
|
-
console.log("Fetching latest release...");
|
|
64
|
-
const res = await httpsGet(`https://api.github.com/repos/${REPO}/releases/latest`);
|
|
65
|
-
if (res.statusCode !== 200) {
|
|
66
|
-
throw new Error(`GitHub API returned HTTP ${res.statusCode}`);
|
|
67
|
-
}
|
|
68
|
-
const chunks = [];
|
|
69
|
-
for await (const chunk of res) {
|
|
70
|
-
chunks.push(chunk);
|
|
71
|
-
}
|
|
72
|
-
const release = JSON.parse(Buffer.concat(chunks).toString());
|
|
73
|
-
const asset = release.assets?.find((a) => a.name === ASSET_NAME);
|
|
74
|
-
if (!asset) {
|
|
75
|
-
throw new Error(`${ASSET_NAME} not found in release ${release.tag_name}`);
|
|
76
|
-
}
|
|
77
|
-
const dest = join(pluginsFolder, ASSET_NAME);
|
|
78
|
-
console.log(`Downloading ${ASSET_NAME} from ${release.tag_name}...`);
|
|
79
|
-
await download(asset.browser_download_url, dest);
|
|
80
|
-
console.log(`Installed to ${dest}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// src/install-plugin-cli.ts
|
|
84
|
-
installPlugin().catch((err) => {
|
|
85
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
86
|
-
process.exitCode = 1;
|
|
87
|
-
});
|