@weppy/roblox-mcp 2.2.1 → 2.3.0
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/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +25 -0
- package/README.md +4 -7
- package/docs/en/installation/README.md +3 -4
- package/docs/es/README.md +2 -5
- package/docs/es/installation/README.md +3 -4
- package/docs/id/README.md +1 -4
- package/docs/id/installation/README.md +3 -4
- package/docs/installer/assets/index-B4Gp7BPj.js +63 -0
- package/docs/installer/assets/index-B7mvmOPt.css +1 -0
- package/docs/installer/index.html +14 -0
- package/docs/installer/manifest.webmanifest +15 -0
- package/docs/installer/sw.js +7 -0
- package/docs/installer/wrox-icon.png +0 -0
- package/docs/ja/README.md +1 -4
- package/docs/ja/installation/README.md +2 -3
- package/docs/ko/README.md +4 -7
- package/docs/ko/installation/README.md +3 -4
- package/docs/pt-br/README.md +2 -5
- package/docs/pt-br/installation/README.md +3 -4
- package/install.ps1 +495 -8
- package/install.sh +499 -9
- package/llms-full.txt +14 -2
- package/llms.txt +9 -3
- package/package.json +1 -1
- package/plugins/weppy-roblox-mcp/.claude-plugin/plugin.json +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ChangelogDetailPage-D7eMrarv.js → ChangelogDetailPage-CGK59Jsx.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ChangelogPage-DFCCRyyK.js → ChangelogPage-oNm6ratx.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ConfirmModal-BmRJ2JXZ.js → ConfirmModal-Dtak3Vnq.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ConnectionPage-CiaCY026.js → ConnectionPage-CjLtImxr.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{InfoLabel-CCDWZLC9.js → InfoLabel-CLvjiyTG.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{OverviewPage-BHpt3LI2.js → OverviewPage-BdF0Ve7h.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{PlaytestPage-CNwwI5Ro.js → PlaytestPage-cQMWlAOS.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{PropertyDiff-DIplDn-J.js → PropertyDiff-BnOZxkTS.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{SettingsPage-CPqQYZPN.js → SettingsPage-C-QX0AY-.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{StatusBadge-C8VKAPpk.js → StatusBadge-A9U9m2LQ.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{SyncPage-DTSKbpio.js → SyncPage-BAS0cXRM.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{TierComparison-7ofkPwj3.js → TierComparison-BA_L4c9p.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{TierPromoProgress-SnRUjAPh.js → TierPromoProgress-Dq6ofjr2.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{ToolsPage-CrdNh3D9.js → ToolsPage-C_tMIyix.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{index-DGGmfli1.js → index-OH9mpHwW.js} +2 -2
- package/plugins/weppy-roblox-mcp/dashboard/dist/assets/{useLiveUptime-BnXeLpOw.js → useLiveUptime-Df1ECedb.js} +1 -1
- package/plugins/weppy-roblox-mcp/dashboard/dist/index.html +1 -1
- package/plugins/weppy-roblox-mcp/dist/index.js +63 -65
- package/plugins/weppy-roblox-mcp/roblox-plugin/WeppyRobloxMCP.rbxm +0 -0
package/install.sh
CHANGED
|
@@ -117,11 +117,479 @@ try {
|
|
|
117
117
|
' >/dev/null 2>&1
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
# Antigravity 설정에 canonical mcpServers 래퍼로 MCP 서버를 추가하고 legacy flat key를 정리
|
|
121
|
+
add_antigravity_mcp_config() {
|
|
122
|
+
local config_path="$1"
|
|
123
|
+
local parent_dir
|
|
124
|
+
parent_dir=$(dirname "$config_path")
|
|
125
|
+
mkdir -p "$parent_dir"
|
|
126
|
+
MCP_CONFIG_PATH="$config_path" node -e '
|
|
127
|
+
const fs = require("fs");
|
|
128
|
+
const configPath = process.env.MCP_CONFIG_PATH;
|
|
129
|
+
let config = {};
|
|
130
|
+
try { config = JSON.parse(fs.readFileSync(configPath, "utf8")); } catch {}
|
|
131
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
132
|
+
config = {};
|
|
133
|
+
}
|
|
134
|
+
const mcpServers = config.mcpServers;
|
|
135
|
+
if (mcpServers !== undefined && (typeof mcpServers !== "object" || mcpServers === null || Array.isArray(mcpServers))) {
|
|
136
|
+
throw new Error("Antigravity mcpServers must be an object");
|
|
137
|
+
}
|
|
138
|
+
const next = { ...config };
|
|
139
|
+
delete next["weppy-roblox-mcp"];
|
|
140
|
+
next.mcpServers = {
|
|
141
|
+
...(mcpServers || {}),
|
|
142
|
+
"weppy-roblox-mcp": { command: "npx", args: ["-y", "@weppy/roblox-mcp"] }
|
|
143
|
+
};
|
|
144
|
+
config = next;
|
|
145
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
146
|
+
'
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
is_antigravity_mcp_configured() {
|
|
150
|
+
local config_path="$1"
|
|
151
|
+
|
|
152
|
+
[ -f "$config_path" ] || return 1
|
|
153
|
+
|
|
154
|
+
MCP_CONFIG_PATH="$config_path" node -e '
|
|
155
|
+
const fs = require("fs");
|
|
156
|
+
const configPath = process.env.MCP_CONFIG_PATH;
|
|
157
|
+
function isJsonObject(value) {
|
|
158
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
159
|
+
}
|
|
160
|
+
function hasExpectedCommandShape(value) {
|
|
161
|
+
return (
|
|
162
|
+
isJsonObject(value) &&
|
|
163
|
+
value.command === "npx" &&
|
|
164
|
+
Array.isArray(value.args) &&
|
|
165
|
+
value.args.length === 2 &&
|
|
166
|
+
value.args[0] === "-y" &&
|
|
167
|
+
value.args[1] === "@weppy/roblox-mcp"
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
172
|
+
const canonical = config?.mcpServers?.["weppy-roblox-mcp"];
|
|
173
|
+
const hasLegacyFlatKey = Object.prototype.hasOwnProperty.call(config, "weppy-roblox-mcp");
|
|
174
|
+
process.exit(hasExpectedCommandShape(canonical) && !hasLegacyFlatKey ? 0 : 1);
|
|
175
|
+
} catch {
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
' >/dev/null 2>&1
|
|
179
|
+
}
|
|
180
|
+
|
|
120
181
|
is_codex_config_configured() {
|
|
121
182
|
local config_path="$1"
|
|
122
183
|
|
|
123
184
|
[ -f "$config_path" ] || return 1
|
|
124
|
-
|
|
185
|
+
MCP_CODEX_CONFIG_PATH="$config_path" node <<'NODE' >/dev/null 2>&1
|
|
186
|
+
const fs = require("fs");
|
|
187
|
+
|
|
188
|
+
const configPath = process.env.MCP_CODEX_CONFIG_PATH;
|
|
189
|
+
const serverName = "weppy-roblox-mcp";
|
|
190
|
+
const expectedCommand = "npx";
|
|
191
|
+
const expectedArgs = ["-y", "@weppy/roblox-mcp"];
|
|
192
|
+
const headerPattern = new RegExp(
|
|
193
|
+
"^\\s*\\[\\s*mcp_servers\\." + serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\s*\\]\\s*(?:#.*)?$",
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
function stripCommentOutsideStrings(line) {
|
|
197
|
+
let inSingle = false;
|
|
198
|
+
let inDouble = false;
|
|
199
|
+
let escaped = false;
|
|
200
|
+
|
|
201
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
202
|
+
const char = line[index];
|
|
203
|
+
|
|
204
|
+
if (char === '"' && !inSingle && !escaped) {
|
|
205
|
+
inDouble = !inDouble;
|
|
206
|
+
} else if (char === "'" && !inDouble && !escaped) {
|
|
207
|
+
inSingle = !inSingle;
|
|
208
|
+
} else if (char === "#" && !inSingle && !inDouble) {
|
|
209
|
+
return line.slice(0, index).trimEnd();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
escaped = char === "\\" && !escaped;
|
|
213
|
+
if (char !== "\\") {
|
|
214
|
+
escaped = false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return line.trimEnd();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function countTripleQuoteToggles(line, quote) {
|
|
222
|
+
let count = 0;
|
|
223
|
+
let inSingle = false;
|
|
224
|
+
let inDouble = false;
|
|
225
|
+
let escaped = false;
|
|
226
|
+
|
|
227
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
228
|
+
const char = line[index] ?? "";
|
|
229
|
+
const nextThree = line.slice(index, index + 3);
|
|
230
|
+
const isOutsideStrings = !inSingle && !inDouble;
|
|
231
|
+
|
|
232
|
+
if (isOutsideStrings && nextThree === quote.repeat(3)) {
|
|
233
|
+
count += 1;
|
|
234
|
+
index += 2;
|
|
235
|
+
escaped = false;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (char === '"' && !inSingle && !escaped) {
|
|
240
|
+
inDouble = !inDouble;
|
|
241
|
+
} else if (char === "'" && !inDouble && !escaped) {
|
|
242
|
+
inSingle = !inSingle;
|
|
243
|
+
} else if (char === "#" && !inSingle && !inDouble) {
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
escaped = char === "\\" && !escaped;
|
|
248
|
+
if (char !== "\\") {
|
|
249
|
+
escaped = false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return count;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function advanceTripleQuoteState(line, state) {
|
|
257
|
+
const next = { ...state };
|
|
258
|
+
const tripleDoubleCount = countTripleQuoteToggles(line, '"');
|
|
259
|
+
const tripleSingleCount = countTripleQuoteToggles(line, "'");
|
|
260
|
+
|
|
261
|
+
if (!next.inTripleSingle && tripleDoubleCount % 2 === 1) {
|
|
262
|
+
next.inTripleDouble = !next.inTripleDouble;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!next.inTripleDouble && tripleSingleCount % 2 === 1) {
|
|
266
|
+
next.inTripleSingle = !next.inTripleSingle;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return next;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function isTomlTableHeaderLine(line) {
|
|
273
|
+
const normalized = stripCommentOutsideStrings(line).trim();
|
|
274
|
+
|
|
275
|
+
if (normalized.length === 0) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return /^\[\[.*\]\]$/.test(normalized) || /^\[.*\]$/.test(normalized);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function findAllCodexBlocks(source) {
|
|
283
|
+
const lines = source.split("\n");
|
|
284
|
+
const blocks = [];
|
|
285
|
+
let activeLines = null;
|
|
286
|
+
let state = {
|
|
287
|
+
inTripleDouble: false,
|
|
288
|
+
inTripleSingle: false,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
for (const line of lines) {
|
|
292
|
+
const isHeaderCandidate = !state.inTripleDouble && !state.inTripleSingle && isTomlTableHeaderLine(line);
|
|
293
|
+
const isCodexHeader = isHeaderCandidate && headerPattern.test(line);
|
|
294
|
+
|
|
295
|
+
if (isCodexHeader) {
|
|
296
|
+
if (activeLines !== null) {
|
|
297
|
+
blocks.push(activeLines.join("\n").trim());
|
|
298
|
+
}
|
|
299
|
+
activeLines = [line];
|
|
300
|
+
} else if (activeLines !== null && isHeaderCandidate) {
|
|
301
|
+
blocks.push(activeLines.join("\n").trim());
|
|
302
|
+
activeLines = null;
|
|
303
|
+
} else if (activeLines !== null) {
|
|
304
|
+
activeLines.push(line);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
state = advanceTripleQuoteState(line, state);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (activeLines !== null) {
|
|
311
|
+
blocks.push(activeLines.join("\n").trim());
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return blocks;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function parseStringAssignment(value, key) {
|
|
318
|
+
const match = new RegExp("^\\s*" + key + "\\s*=\\s*([\"'])([^\"']+)\\1\\s*$").exec(value);
|
|
319
|
+
return match ? match[2] : null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function parseTomlStringArray(value) {
|
|
323
|
+
const match = /^\s*args\s*=\s*\[(.*)\]\s*$/ms.exec(value.trim());
|
|
324
|
+
|
|
325
|
+
if (match === null) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const body = match[1] ?? "";
|
|
330
|
+
const values = [];
|
|
331
|
+
let cursor = 0;
|
|
332
|
+
let expectValue = true;
|
|
333
|
+
|
|
334
|
+
while (cursor < body.length) {
|
|
335
|
+
while (cursor < body.length && /\s/.test(body[cursor] ?? "")) {
|
|
336
|
+
cursor += 1;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (cursor >= body.length) {
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (!expectValue) {
|
|
344
|
+
if (body[cursor] !== ",") {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
cursor += 1;
|
|
348
|
+
expectValue = true;
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const quote = body[cursor];
|
|
353
|
+
if (quote !== '"' && quote !== "'") {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
cursor += 1;
|
|
358
|
+
let token = "";
|
|
359
|
+
let escaped = false;
|
|
360
|
+
|
|
361
|
+
while (cursor < body.length) {
|
|
362
|
+
const char = body[cursor] ?? "";
|
|
363
|
+
|
|
364
|
+
if (char === quote && !escaped) {
|
|
365
|
+
cursor += 1;
|
|
366
|
+
values.push(token);
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
token += char;
|
|
371
|
+
escaped = char === "\\" && !escaped;
|
|
372
|
+
if (char !== "\\") {
|
|
373
|
+
escaped = false;
|
|
374
|
+
}
|
|
375
|
+
cursor += 1;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (cursor > body.length) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
expectValue = false;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const leftover = body.slice(cursor).trim();
|
|
386
|
+
if (leftover === ",") {
|
|
387
|
+
return values;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return leftover.length === 0 ? values : null;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function collectArrayLines(lines, startIndex) {
|
|
394
|
+
const collected = [stripCommentOutsideStrings(lines[startIndex] ?? "")];
|
|
395
|
+
let bracketDepth = 0;
|
|
396
|
+
let inSingle = false;
|
|
397
|
+
let inDouble = false;
|
|
398
|
+
let escaped = false;
|
|
399
|
+
|
|
400
|
+
for (let lineIndex = startIndex; lineIndex < lines.length; lineIndex += 1) {
|
|
401
|
+
const sanitized = stripCommentOutsideStrings(lines[lineIndex] ?? "");
|
|
402
|
+
if (lineIndex !== startIndex) {
|
|
403
|
+
collected.push(sanitized);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
for (let index = 0; index < sanitized.length; index += 1) {
|
|
407
|
+
const char = sanitized[index] ?? "";
|
|
408
|
+
|
|
409
|
+
if (char === '"' && !inSingle && !escaped) {
|
|
410
|
+
inDouble = !inDouble;
|
|
411
|
+
} else if (char === "'" && !inDouble && !escaped) {
|
|
412
|
+
inSingle = !inSingle;
|
|
413
|
+
} else if (!inSingle && !inDouble) {
|
|
414
|
+
if (char === "[") {
|
|
415
|
+
bracketDepth += 1;
|
|
416
|
+
} else if (char === "]") {
|
|
417
|
+
bracketDepth -= 1;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
escaped = char === "\\" && !escaped;
|
|
422
|
+
if (char !== "\\") {
|
|
423
|
+
escaped = false;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (bracketDepth <= 0) {
|
|
428
|
+
return {
|
|
429
|
+
nextIndex: lineIndex,
|
|
430
|
+
text: collected.join("\n"),
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function parseCodexBlock(blockContent) {
|
|
439
|
+
const lines = blockContent.split("\n");
|
|
440
|
+
let command = null;
|
|
441
|
+
let args = null;
|
|
442
|
+
let hasConflict = false;
|
|
443
|
+
let inTripleDouble = false;
|
|
444
|
+
let inTripleSingle = false;
|
|
445
|
+
|
|
446
|
+
for (let index = 1; index < lines.length; index += 1) {
|
|
447
|
+
const line = lines[index] ?? "";
|
|
448
|
+
const sanitized = stripCommentOutsideStrings(line);
|
|
449
|
+
const trimmed = sanitized.trim();
|
|
450
|
+
|
|
451
|
+
if (inTripleDouble) {
|
|
452
|
+
if (countTripleQuoteToggles(sanitized, '"') % 2 === 1) {
|
|
453
|
+
inTripleDouble = false;
|
|
454
|
+
}
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (inTripleSingle) {
|
|
459
|
+
if (countTripleQuoteToggles(sanitized, "'") % 2 === 1) {
|
|
460
|
+
inTripleSingle = false;
|
|
461
|
+
}
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (countTripleQuoteToggles(sanitized, '"') % 2 === 1) {
|
|
466
|
+
inTripleDouble = true;
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (countTripleQuoteToggles(sanitized, "'") % 2 === 1) {
|
|
471
|
+
inTripleSingle = true;
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (trimmed.length === 0) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (/^command\s*=/.test(trimmed)) {
|
|
480
|
+
const parsedCommand = parseStringAssignment(trimmed, "command");
|
|
481
|
+
if (command !== null || parsedCommand === null) {
|
|
482
|
+
hasConflict = true;
|
|
483
|
+
} else {
|
|
484
|
+
command = parsedCommand;
|
|
485
|
+
}
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (/^args\s*=/.test(trimmed)) {
|
|
490
|
+
const collected = collectArrayLines(lines, index);
|
|
491
|
+
const parsedArgs = collected === null ? null : parseTomlStringArray(collected.text);
|
|
492
|
+
|
|
493
|
+
if (args !== null || parsedArgs === null || collected === null) {
|
|
494
|
+
hasConflict = true;
|
|
495
|
+
} else {
|
|
496
|
+
args = parsedArgs;
|
|
497
|
+
index = collected.nextIndex;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return {
|
|
503
|
+
args,
|
|
504
|
+
command,
|
|
505
|
+
hasConflict,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function isStructurallySafe(source) {
|
|
510
|
+
let bracketDepth = 0;
|
|
511
|
+
let braceDepth = 0;
|
|
512
|
+
let inSingle = false;
|
|
513
|
+
let inDouble = false;
|
|
514
|
+
let escaped = false;
|
|
515
|
+
let tripleState = {
|
|
516
|
+
inTripleDouble: false,
|
|
517
|
+
inTripleSingle: false,
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
for (const line of source.split("\n")) {
|
|
521
|
+
tripleState = advanceTripleQuoteState(line, tripleState);
|
|
522
|
+
|
|
523
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
524
|
+
const char = line[index] ?? "";
|
|
525
|
+
|
|
526
|
+
if (!inSingle && !inDouble && char === "#") {
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (char === '"' && !inSingle && !escaped) {
|
|
531
|
+
inDouble = !inDouble;
|
|
532
|
+
} else if (char === "'" && !inDouble && !escaped) {
|
|
533
|
+
inSingle = !inSingle;
|
|
534
|
+
} else if (!inSingle && !inDouble) {
|
|
535
|
+
if (char === "[") {
|
|
536
|
+
bracketDepth += 1;
|
|
537
|
+
} else if (char === "]") {
|
|
538
|
+
bracketDepth -= 1;
|
|
539
|
+
if (bracketDepth < 0) {
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
} else if (char === "{") {
|
|
543
|
+
braceDepth += 1;
|
|
544
|
+
} else if (char === "}") {
|
|
545
|
+
braceDepth -= 1;
|
|
546
|
+
if (braceDepth < 0) {
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
escaped = char === "\\" && !escaped;
|
|
553
|
+
if (char !== "\\") {
|
|
554
|
+
escaped = false;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return (
|
|
560
|
+
!tripleState.inTripleDouble &&
|
|
561
|
+
!tripleState.inTripleSingle &&
|
|
562
|
+
bracketDepth === 0 &&
|
|
563
|
+
braceDepth === 0 &&
|
|
564
|
+
!inSingle &&
|
|
565
|
+
!inDouble
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
try {
|
|
570
|
+
const source = fs.readFileSync(configPath, "utf8");
|
|
571
|
+
if (!isStructurallySafe(source)) {
|
|
572
|
+
process.exit(1);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const blocks = findAllCodexBlocks(source);
|
|
576
|
+
if (blocks.length !== 1) {
|
|
577
|
+
process.exit(1);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const parsed = parseCodexBlock(blocks[0]);
|
|
581
|
+
const isConfigured =
|
|
582
|
+
!parsed.hasConflict &&
|
|
583
|
+
parsed.command === expectedCommand &&
|
|
584
|
+
Array.isArray(parsed.args) &&
|
|
585
|
+
parsed.args.length === expectedArgs.length &&
|
|
586
|
+
parsed.args.every((entry, index) => entry === expectedArgs[index]);
|
|
587
|
+
|
|
588
|
+
process.exit(isConfigured ? 0 : 1);
|
|
589
|
+
} catch {
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
NODE
|
|
125
593
|
}
|
|
126
594
|
|
|
127
595
|
resolve_optional_cli_command() {
|
|
@@ -236,8 +704,7 @@ fi
|
|
|
236
704
|
# [3/3] Register MCP with AI apps
|
|
237
705
|
# ═══════════════════════════════════
|
|
238
706
|
step "3/3" "Register MCP with AI apps"
|
|
239
|
-
printf " Automatic registration: Claude Code, Claude Desktop, Cursor, Codex CLI, Gemini CLI\n"
|
|
240
|
-
printf " Manual setup required: Codex App, Antigravity\n"
|
|
707
|
+
printf " Automatic registration: Claude Code, Claude Desktop, Cursor, Codex CLI/App, Gemini CLI, Antigravity\n"
|
|
241
708
|
|
|
242
709
|
MCP_COMMAND='npx -y @weppy/roblox-mcp'
|
|
243
710
|
|
|
@@ -279,17 +746,17 @@ else
|
|
|
279
746
|
NOT_DETECTED+=("Cursor (not found)")
|
|
280
747
|
fi
|
|
281
748
|
|
|
282
|
-
# Codex CLI
|
|
749
|
+
# Codex CLI / Codex App (share the same ~/.codex/config.toml)
|
|
283
750
|
CODEX_CONFIG="$HOME/.codex/config.toml"
|
|
284
751
|
CODEX_CLI_COMMAND="$(resolve_optional_cli_command codex 2>/dev/null || true)"
|
|
285
752
|
if is_codex_config_configured "$CODEX_CONFIG"; then
|
|
286
|
-
DETECTED_NAMES+=("Codex CLI (configured)")
|
|
753
|
+
DETECTED_NAMES+=("Codex CLI/App (configured)")
|
|
287
754
|
DETECTED_TYPES+=("codex-cli")
|
|
288
755
|
elif [ -n "$CODEX_CLI_COMMAND" ]; then
|
|
289
|
-
DETECTED_NAMES+=("Codex CLI")
|
|
756
|
+
DETECTED_NAMES+=("Codex CLI/App")
|
|
290
757
|
DETECTED_TYPES+=("codex-cli")
|
|
291
758
|
else
|
|
292
|
-
NOT_DETECTED+=("Codex CLI (not found)")
|
|
759
|
+
NOT_DETECTED+=("Codex CLI/App (not found)")
|
|
293
760
|
fi
|
|
294
761
|
|
|
295
762
|
# Gemini CLI
|
|
@@ -306,6 +773,21 @@ else
|
|
|
306
773
|
NOT_DETECTED+=("Gemini CLI (not found)")
|
|
307
774
|
fi
|
|
308
775
|
|
|
776
|
+
# Antigravity (unofficial path, auto-register if found)
|
|
777
|
+
ANTIGRAVITY_CONFIG="$HOME/.gemini/antigravity/mcp_config.json"
|
|
778
|
+
if is_antigravity_mcp_configured "$ANTIGRAVITY_CONFIG"; then
|
|
779
|
+
DETECTED_NAMES+=("Antigravity (configured)")
|
|
780
|
+
DETECTED_TYPES+=("antigravity")
|
|
781
|
+
elif [ -f "$ANTIGRAVITY_CONFIG" ]; then
|
|
782
|
+
DETECTED_NAMES+=("Antigravity")
|
|
783
|
+
DETECTED_TYPES+=("antigravity")
|
|
784
|
+
elif [ -d "$HOME/.gemini/antigravity" ]; then
|
|
785
|
+
DETECTED_NAMES+=("Antigravity")
|
|
786
|
+
DETECTED_TYPES+=("antigravity")
|
|
787
|
+
else
|
|
788
|
+
NOT_DETECTED+=("Antigravity (not found)")
|
|
789
|
+
fi
|
|
790
|
+
|
|
309
791
|
if [ ${#DETECTED_NAMES[@]} -eq 0 ]; then
|
|
310
792
|
warn "No AI apps detected"
|
|
311
793
|
info "Register MCP server manually: $MCP_COMMAND"
|
|
@@ -411,6 +893,15 @@ else
|
|
|
411
893
|
fail "Failed: $app_name"
|
|
412
894
|
fi
|
|
413
895
|
;;
|
|
896
|
+
antigravity)
|
|
897
|
+
if is_antigravity_mcp_configured "$ANTIGRAVITY_CONFIG"; then
|
|
898
|
+
success "Already configured: $app_name"
|
|
899
|
+
elif add_antigravity_mcp_config "$ANTIGRAVITY_CONFIG"; then
|
|
900
|
+
success "Registered: $app_name"
|
|
901
|
+
else
|
|
902
|
+
fail "Failed: $app_name"
|
|
903
|
+
fi
|
|
904
|
+
;;
|
|
414
905
|
esac
|
|
415
906
|
done
|
|
416
907
|
fi
|
|
@@ -428,7 +919,6 @@ printf " 1. Restart Roblox Studio\n"
|
|
|
428
919
|
# shellcheck disable=SC2059
|
|
429
920
|
printf " 2. Look for the ${BOLD}WROX${NC} button in the Plugins tab\n"
|
|
430
921
|
printf " 3. Click Connect and start building with AI!\n\n"
|
|
431
|
-
printf " Auto registration: Claude Code, Claude Desktop, Cursor, Codex CLI, Gemini CLI\n"
|
|
432
|
-
printf " Manual setup required: Codex App, Antigravity\n\n"
|
|
922
|
+
printf " Auto registration: Claude Code, Claude Desktop, Cursor, Codex CLI/App, Gemini CLI, Antigravity\n\n"
|
|
433
923
|
# shellcheck disable=SC2059
|
|
434
924
|
printf " ${DIM}Docs: https://github.com/hope1026/weppy-roblox-mcp${NC}\n\n"
|
package/llms-full.txt
CHANGED
|
@@ -21,9 +21,21 @@ Roblox Studio Plugin ← Executes commands inside Studio
|
|
|
21
21
|
|
|
22
22
|
When an AI app says "Create a blue part", the MCP server converts this request, and the Roblox Studio plugin actually creates the part.
|
|
23
23
|
|
|
24
|
-
##
|
|
24
|
+
## Web Installer (Recommended)
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Open the installer page in Chrome, Edge, or Arc for one-click setup:
|
|
27
|
+
|
|
28
|
+
https://hope1026.github.io/weppy-roblox-mcp/installer/
|
|
29
|
+
|
|
30
|
+
It writes the supported AI app config for `npx -y @weppy/roblox-mcp` first.
|
|
31
|
+
|
|
32
|
+
After you reopen the AI app, the first MCP run fetches the package through `npx` and auto-installs the Roblox Studio plugin.
|
|
33
|
+
|
|
34
|
+
For Safari or Firefox, use the installer page as a guide or continue with the manual install below.
|
|
35
|
+
|
|
36
|
+
## One-Line Install (Fallback)
|
|
37
|
+
|
|
38
|
+
Advanced users can install everything in one command:
|
|
27
39
|
|
|
28
40
|
```bash
|
|
29
41
|
# macOS / Linux
|
package/llms.txt
CHANGED
|
@@ -21,7 +21,11 @@ terrain, and more — inside a live Studio session.
|
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
23
23
|
|
|
24
|
-
**
|
|
24
|
+
**Web installer** (recommended one-click setup for Chrome, Edge, and Arc):
|
|
25
|
+
|
|
26
|
+
https://hope1026.github.io/weppy-roblox-mcp/installer/
|
|
27
|
+
|
|
28
|
+
**One-line install** (advanced fallback):
|
|
25
29
|
|
|
26
30
|
```bash
|
|
27
31
|
# macOS / Linux
|
|
@@ -31,8 +35,10 @@ curl -fsSL https://raw.githubusercontent.com/hope1026/weppy-roblox-mcp/main/inst
|
|
|
31
35
|
irm https://raw.githubusercontent.com/hope1026/weppy-roblox-mcp/main/install.ps1 | iex
|
|
32
36
|
```
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
Safari / Firefox users should follow the installer page guide or open the installation docs.
|
|
39
|
+
|
|
40
|
+
Manual alternative:
|
|
41
|
+
1. Install Roblox Studio plugin via the installation guide
|
|
36
42
|
2. Register MCP server: `npx -y @weppy/roblox-mcp`
|
|
37
43
|
|
|
38
44
|
## Tiers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weppy/roblox-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "MCP (Model Context Protocol) server for Roblox Studio integration - enables AI coding agents to interact with Roblox Studio in real-time",
|
|
5
5
|
"main": "plugins/weppy-roblox-mcp/dist/index.js",
|
|
6
6
|
"type": "module",
|