@weppy/roblox-mcp 2.2.2 → 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 +17 -0
- package/README.md +3 -5
- package/docs/en/installation/README.md +2 -2
- package/docs/es/README.md +1 -3
- package/docs/es/installation/README.md +2 -2
- package/docs/id/README.md +0 -2
- package/docs/id/installation/README.md +2 -2
- 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 +0 -2
- package/docs/ja/installation/README.md +1 -1
- package/docs/ko/README.md +3 -5
- package/docs/ko/installation/README.md +2 -2
- package/docs/pt-br/README.md +1 -3
- package/docs/pt-br/installation/README.md +2 -2
- package/install.ps1 +434 -4
- package/install.sh +439 -4
- 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/docs/ko/README.md
CHANGED
|
@@ -18,8 +18,6 @@ Claude, Codex, Gemini 같은 AI 코딩 에이전트는 강력하지만 — Roblo
|
|
|
18
18
|
|
|
19
19
|
## 빠른 설치
|
|
20
20
|
|
|
21
|
-
**원라인 설치** — MCP 서버, Roblox Studio 플러그인, AI 앱 등록을 한 번에 진행합니다:
|
|
22
|
-
|
|
23
21
|
**macOS / Linux**
|
|
24
22
|
|
|
25
23
|
```bash
|
|
@@ -32,15 +30,15 @@ curl -fsSL https://raw.githubusercontent.com/hope1026/weppy-roblox-mcp/main/inst
|
|
|
32
30
|
irm https://raw.githubusercontent.com/hope1026/weppy-roblox-mcp/main/install.ps1 | iex
|
|
33
31
|
```
|
|
34
32
|
|
|
35
|
-
Roblox Studio를
|
|
33
|
+
AI 앱을 다시 열고 Roblox Studio를 재시작하세요.
|
|
36
34
|
|
|
37
35
|
자동 MCP 등록은 **Claude Code, Claude Desktop, Cursor, Codex CLI/App, Gemini CLI, Antigravity**를 지원합니다.
|
|
38
36
|
|
|
39
|
-
Windows에서 PowerShell 실행이 차단되면 아래
|
|
37
|
+
Windows에서 PowerShell 실행이 차단되면 아래 설치 가이드를 사용하세요. ZIP 패키지를 사용하는 경우 `setup-plugin.bat`, `setup-mcp.bat`도 사용할 수 있습니다.
|
|
40
38
|
|
|
41
39
|
### 수동 설치
|
|
42
40
|
|
|
43
|
-
원라인 설치가 동작하지 않거나 환경상 자동 설치를 사용할 수 없는 경우, 아래
|
|
41
|
+
원라인 설치가 동작하지 않거나 환경상 자동 설치를 사용할 수 없는 경우, 아래 설치 가이드를 대안으로 진행하세요.
|
|
44
42
|
|
|
45
43
|
**1단계** — Roblox Studio 플러그인 설치 (Studio와 AI를 연결하는 다리):
|
|
46
44
|
[플러그인 설치 가이드](installation/roblox-plugin.md)
|
|
@@ -16,7 +16,7 @@ AI 앱이 "파란 파트를 만들어줘"라고 하면, MCP 서버가 이 요청
|
|
|
16
16
|
|
|
17
17
|
## 원라인 설치 (권장)
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
아래 명령어 한 줄로 MCP 서버와 플러그인을 한 번에 설치합니다:
|
|
20
20
|
|
|
21
21
|
**macOS / Linux**
|
|
22
22
|
|
|
@@ -30,7 +30,7 @@ curl -fsSL https://raw.githubusercontent.com/hope1026/weppy-roblox-mcp/main/inst
|
|
|
30
30
|
irm https://raw.githubusercontent.com/hope1026/weppy-roblox-mcp/main/install.ps1 | iex
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Roblox Studio를
|
|
33
|
+
AI 앱을 다시 열고 Roblox Studio를 재시작하세요.
|
|
34
34
|
|
|
35
35
|
설치 중 오류가 발생하면 임시 로그 파일 경로를 출력하고, 대화형 실행에서는 종료 전에 콘솔을 유지해서 원인을 확인할 수 있습니다.
|
|
36
36
|
|
package/docs/pt-br/README.md
CHANGED
|
@@ -18,8 +18,6 @@ Sem necessidade de copiar e colar código. A IA trabalha e você confere os resu
|
|
|
18
18
|
|
|
19
19
|
## Instalacao rapida
|
|
20
20
|
|
|
21
|
-
**Instalacao em um comando** — Instala o servidor MCP, o plugin do Roblox Studio e registra nos apps de IA em um único passo:
|
|
22
|
-
|
|
23
21
|
**macOS / Linux**
|
|
24
22
|
|
|
25
23
|
```bash
|
|
@@ -40,7 +38,7 @@ Se a execução do PowerShell estiver bloqueada no Windows, siga a instalação
|
|
|
40
38
|
|
|
41
39
|
### Instalacao manual
|
|
42
40
|
|
|
43
|
-
Se a
|
|
41
|
+
Se a instalacao em um comando nao funcionar, ou se a instalacao automatica nao puder ser usada no seu ambiente, use a instalacao manual abaixo como alternativa.
|
|
44
42
|
|
|
45
43
|
**Passo 1** — Instale o plugin do Roblox Studio (ponte entre Studio e IA):
|
|
46
44
|
[Guia de instalacao do plugin](installation/roblox-plugin.md)
|
|
@@ -16,7 +16,7 @@ Quando o app de IA diz "cria uma parte azul", o servidor MCP converte o pedido e
|
|
|
16
16
|
|
|
17
17
|
## Instalação em um comando (Recomendado)
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Instale tudo com um único comando:
|
|
20
20
|
|
|
21
21
|
**macOS / Linux**
|
|
22
22
|
|
|
@@ -40,7 +40,7 @@ Se a execução do PowerShell estiver bloqueada no Windows, siga a instalação
|
|
|
40
40
|
|
|
41
41
|
## Instalação manual
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
Se a instalacao em um comando nao funcionar, ou quando a instalacao automatica nao puder ser usada no seu ambiente, use a instalacao manual abaixo como alternativa.
|
|
44
44
|
|
|
45
45
|
### Passo 1: Instalar plugin do Roblox Studio
|
|
46
46
|
|
package/install.ps1
CHANGED
|
@@ -139,7 +139,7 @@ function Test-McpJsonConfigured($configPath) {
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
#
|
|
142
|
+
# Antigravity 설정에 canonical mcpServers 래퍼로 MCP 서버를 추가하고 legacy flat key를 정리
|
|
143
143
|
function Add-AntigravityMcpConfig($configPath) {
|
|
144
144
|
$parentDir = Split-Path $configPath -Parent
|
|
145
145
|
if (-not (Test-Path $parentDir)) { New-Item -ItemType Directory -Path $parentDir -Force | Out-Null }
|
|
@@ -150,7 +150,20 @@ const fs = require('fs');
|
|
|
150
150
|
const configPath = process.env.MCP_CONFIG_PATH;
|
|
151
151
|
let config = {};
|
|
152
152
|
try { config = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch {}
|
|
153
|
-
config
|
|
153
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
154
|
+
config = {};
|
|
155
|
+
}
|
|
156
|
+
const mcpServers = config.mcpServers;
|
|
157
|
+
if (mcpServers !== undefined && (typeof mcpServers !== 'object' || mcpServers === null || Array.isArray(mcpServers))) {
|
|
158
|
+
throw new Error('Antigravity mcpServers must be an object');
|
|
159
|
+
}
|
|
160
|
+
const next = { ...config };
|
|
161
|
+
delete next['weppy-roblox-mcp'];
|
|
162
|
+
next.mcpServers = {
|
|
163
|
+
...(mcpServers || {}),
|
|
164
|
+
'weppy-roblox-mcp': { command: 'npx', args: ['-y', '@weppy/roblox-mcp'] }
|
|
165
|
+
};
|
|
166
|
+
config = next;
|
|
154
167
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
155
168
|
"@
|
|
156
169
|
} finally {
|
|
@@ -165,7 +178,10 @@ function Test-AntigravityMcpConfigured($configPath) {
|
|
|
165
178
|
|
|
166
179
|
try {
|
|
167
180
|
$config = Get-Content -Path $configPath -Raw | ConvertFrom-Json
|
|
168
|
-
|
|
181
|
+
$hasLegacyFlatKey = $config.PSObject.Properties.Name -contains 'weppy-roblox-mcp'
|
|
182
|
+
$server = $config.mcpServers.'weppy-roblox-mcp'
|
|
183
|
+
$hasCanonicalArgs = ($server.args -is [System.Array]) -and ($server.args.Count -eq 2) -and ($server.args[0] -eq '-y') -and ($server.args[1] -eq '@weppy/roblox-mcp')
|
|
184
|
+
return ($server.command -eq 'npx') -and $hasCanonicalArgs -and (-not $hasLegacyFlatKey)
|
|
169
185
|
}
|
|
170
186
|
catch {
|
|
171
187
|
return $false
|
|
@@ -177,7 +193,421 @@ function Test-CodexConfigConfigured($configPath) {
|
|
|
177
193
|
return $false
|
|
178
194
|
}
|
|
179
195
|
|
|
180
|
-
|
|
196
|
+
$env:MCP_CODEX_CONFIG_PATH = $configPath
|
|
197
|
+
try {
|
|
198
|
+
node -e @"
|
|
199
|
+
const fs = require('fs');
|
|
200
|
+
|
|
201
|
+
const configPath = process.env.MCP_CODEX_CONFIG_PATH;
|
|
202
|
+
const serverName = 'weppy-roblox-mcp';
|
|
203
|
+
const expectedCommand = 'npx';
|
|
204
|
+
const expectedArgs = ['-y', '@weppy/roblox-mcp'];
|
|
205
|
+
const headerPattern = new RegExp(
|
|
206
|
+
'^\\s*\\[\\s*mcp_servers\\.' + serverName.replace(/[.*+?^${}()|[\]\\\\]/g, '\\$&') + '\\s*\\]\\s*(?:#.*)?$'
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
function stripCommentOutsideStrings(line) {
|
|
210
|
+
let inSingle = false;
|
|
211
|
+
let inDouble = false;
|
|
212
|
+
let escaped = false;
|
|
213
|
+
|
|
214
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
215
|
+
const char = line[index];
|
|
216
|
+
|
|
217
|
+
if (char === '"' && !inSingle && !escaped) {
|
|
218
|
+
inDouble = !inDouble;
|
|
219
|
+
} else if (char === "'" && !inDouble && !escaped) {
|
|
220
|
+
inSingle = !inSingle;
|
|
221
|
+
} else if (char === '#' && !inSingle && !inDouble) {
|
|
222
|
+
return line.slice(0, index).trimEnd();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
escaped = char === '\\' && !escaped;
|
|
226
|
+
if (char !== '\\') {
|
|
227
|
+
escaped = false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return line.trimEnd();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function countTripleQuoteToggles(line, quote) {
|
|
235
|
+
let count = 0;
|
|
236
|
+
let inSingle = false;
|
|
237
|
+
let inDouble = false;
|
|
238
|
+
let escaped = false;
|
|
239
|
+
|
|
240
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
241
|
+
const char = line[index] ?? '';
|
|
242
|
+
const nextThree = line.slice(index, index + 3);
|
|
243
|
+
const isOutsideStrings = !inSingle && !inDouble;
|
|
244
|
+
|
|
245
|
+
if (isOutsideStrings && nextThree === quote.repeat(3)) {
|
|
246
|
+
count += 1;
|
|
247
|
+
index += 2;
|
|
248
|
+
escaped = false;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (char === '"' && !inSingle && !escaped) {
|
|
253
|
+
inDouble = !inDouble;
|
|
254
|
+
} else if (char === "'" && !inDouble && !escaped) {
|
|
255
|
+
inSingle = !inSingle;
|
|
256
|
+
} else if (char === '#' && !inSingle && !inDouble) {
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
escaped = char === '\\' && !escaped;
|
|
261
|
+
if (char !== '\\') {
|
|
262
|
+
escaped = false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return count;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function advanceTripleQuoteState(line, state) {
|
|
270
|
+
const next = { ...state };
|
|
271
|
+
const tripleDoubleCount = countTripleQuoteToggles(line, '"');
|
|
272
|
+
const tripleSingleCount = countTripleQuoteToggles(line, "'");
|
|
273
|
+
|
|
274
|
+
if (!next.inTripleSingle && tripleDoubleCount % 2 === 1) {
|
|
275
|
+
next.inTripleDouble = !next.inTripleDouble;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!next.inTripleDouble && tripleSingleCount % 2 === 1) {
|
|
279
|
+
next.inTripleSingle = !next.inTripleSingle;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return next;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function isTomlTableHeaderLine(line) {
|
|
286
|
+
const normalized = stripCommentOutsideStrings(line).trim();
|
|
287
|
+
|
|
288
|
+
if (normalized.length === 0) {
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return /^\[\[.*\]\]$/.test(normalized) || /^\[.*\]$/.test(normalized);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function findAllCodexBlocks(source) {
|
|
296
|
+
const lines = source.split('\n');
|
|
297
|
+
const blocks = [];
|
|
298
|
+
let activeLines = null;
|
|
299
|
+
let state = {
|
|
300
|
+
inTripleDouble: false,
|
|
301
|
+
inTripleSingle: false,
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
for (const line of lines) {
|
|
305
|
+
const isHeaderCandidate = !state.inTripleDouble && !state.inTripleSingle && isTomlTableHeaderLine(line);
|
|
306
|
+
const isCodexHeader = isHeaderCandidate && headerPattern.test(line);
|
|
307
|
+
|
|
308
|
+
if (isCodexHeader) {
|
|
309
|
+
if (activeLines !== null) {
|
|
310
|
+
blocks.push(activeLines.join('\n').trim());
|
|
311
|
+
}
|
|
312
|
+
activeLines = [line];
|
|
313
|
+
} else if (activeLines !== null && isHeaderCandidate) {
|
|
314
|
+
blocks.push(activeLines.join('\n').trim());
|
|
315
|
+
activeLines = null;
|
|
316
|
+
} else if (activeLines !== null) {
|
|
317
|
+
activeLines.push(line);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
state = advanceTripleQuoteState(line, state);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (activeLines !== null) {
|
|
324
|
+
blocks.push(activeLines.join('\n').trim());
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return blocks;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function parseStringAssignment(value, key) {
|
|
331
|
+
const match = new RegExp('^\\s*' + key + '\\s*=\\s*(["\\'])([^"\\']+)\\1\\s*$').exec(value);
|
|
332
|
+
return match ? match[2] : null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function parseTomlStringArray(value) {
|
|
336
|
+
const match = /^\s*args\s*=\s*\[(.*)\]\s*$/ms.exec(value.trim());
|
|
337
|
+
|
|
338
|
+
if (match === null) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const body = match[1] ?? '';
|
|
343
|
+
const values = [];
|
|
344
|
+
let cursor = 0;
|
|
345
|
+
let expectValue = true;
|
|
346
|
+
|
|
347
|
+
while (cursor < body.length) {
|
|
348
|
+
while (cursor < body.length && /\s/.test(body[cursor] ?? '')) {
|
|
349
|
+
cursor += 1;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (cursor >= body.length) {
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!expectValue) {
|
|
357
|
+
if (body[cursor] !== ',') {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
cursor += 1;
|
|
361
|
+
expectValue = true;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const quote = body[cursor];
|
|
366
|
+
if (quote !== '"' && quote !== "'") {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
cursor += 1;
|
|
371
|
+
let token = '';
|
|
372
|
+
let escaped = false;
|
|
373
|
+
|
|
374
|
+
while (cursor < body.length) {
|
|
375
|
+
const char = body[cursor] ?? '';
|
|
376
|
+
|
|
377
|
+
if (char === quote && !escaped) {
|
|
378
|
+
cursor += 1;
|
|
379
|
+
values.push(token);
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
token += char;
|
|
384
|
+
escaped = char === '\\' && !escaped;
|
|
385
|
+
if (char !== '\\') {
|
|
386
|
+
escaped = false;
|
|
387
|
+
}
|
|
388
|
+
cursor += 1;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (cursor > body.length) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
expectValue = false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const leftover = body.slice(cursor).trim();
|
|
399
|
+
if (leftover === ',') {
|
|
400
|
+
return values;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return leftover.length === 0 ? values : null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function collectArrayLines(lines, startIndex) {
|
|
407
|
+
const collected = [stripCommentOutsideStrings(lines[startIndex] ?? '')];
|
|
408
|
+
let bracketDepth = 0;
|
|
409
|
+
let inSingle = false;
|
|
410
|
+
let inDouble = false;
|
|
411
|
+
let escaped = false;
|
|
412
|
+
|
|
413
|
+
for (let lineIndex = startIndex; lineIndex < lines.length; lineIndex += 1) {
|
|
414
|
+
const sanitized = stripCommentOutsideStrings(lines[lineIndex] ?? '');
|
|
415
|
+
if (lineIndex !== startIndex) {
|
|
416
|
+
collected.push(sanitized);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
for (let index = 0; index < sanitized.length; index += 1) {
|
|
420
|
+
const char = sanitized[index] ?? '';
|
|
421
|
+
|
|
422
|
+
if (char === '"' && !inSingle && !escaped) {
|
|
423
|
+
inDouble = !inDouble;
|
|
424
|
+
} else if (char === "'" && !inDouble && !escaped) {
|
|
425
|
+
inSingle = !inSingle;
|
|
426
|
+
} else if (!inSingle && !inDouble) {
|
|
427
|
+
if (char === '[') {
|
|
428
|
+
bracketDepth += 1;
|
|
429
|
+
} else if (char === ']') {
|
|
430
|
+
bracketDepth -= 1;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
escaped = char === '\\' && !escaped;
|
|
435
|
+
if (char !== '\\') {
|
|
436
|
+
escaped = false;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (bracketDepth <= 0) {
|
|
441
|
+
return {
|
|
442
|
+
nextIndex: lineIndex,
|
|
443
|
+
text: collected.join('\n'),
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function parseCodexBlock(blockContent) {
|
|
452
|
+
const lines = blockContent.split('\n');
|
|
453
|
+
let command = null;
|
|
454
|
+
let args = null;
|
|
455
|
+
let hasConflict = false;
|
|
456
|
+
let inTripleDouble = false;
|
|
457
|
+
let inTripleSingle = false;
|
|
458
|
+
|
|
459
|
+
for (let index = 1; index < lines.length; index += 1) {
|
|
460
|
+
const line = lines[index] ?? '';
|
|
461
|
+
const sanitized = stripCommentOutsideStrings(line);
|
|
462
|
+
const trimmed = sanitized.trim();
|
|
463
|
+
|
|
464
|
+
if (inTripleDouble) {
|
|
465
|
+
if (countTripleQuoteToggles(sanitized, '"') % 2 === 1) {
|
|
466
|
+
inTripleDouble = false;
|
|
467
|
+
}
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (inTripleSingle) {
|
|
472
|
+
if (countTripleQuoteToggles(sanitized, "'") % 2 === 1) {
|
|
473
|
+
inTripleSingle = false;
|
|
474
|
+
}
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (countTripleQuoteToggles(sanitized, '"') % 2 === 1) {
|
|
479
|
+
inTripleDouble = true;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (countTripleQuoteToggles(sanitized, "'") % 2 === 1) {
|
|
484
|
+
inTripleSingle = true;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (trimmed.length === 0) {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (/^command\s*=/.test(trimmed)) {
|
|
493
|
+
const parsedCommand = parseStringAssignment(trimmed, 'command');
|
|
494
|
+
if (command !== null || parsedCommand === null) {
|
|
495
|
+
hasConflict = true;
|
|
496
|
+
} else {
|
|
497
|
+
command = parsedCommand;
|
|
498
|
+
}
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (/^args\s*=/.test(trimmed)) {
|
|
503
|
+
const collected = collectArrayLines(lines, index);
|
|
504
|
+
const parsedArgs = collected === null ? null : parseTomlStringArray(collected.text);
|
|
505
|
+
|
|
506
|
+
if (args !== null || parsedArgs === null || collected === null) {
|
|
507
|
+
hasConflict = true;
|
|
508
|
+
} else {
|
|
509
|
+
args = parsedArgs;
|
|
510
|
+
index = collected.nextIndex;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return {
|
|
516
|
+
args,
|
|
517
|
+
command,
|
|
518
|
+
hasConflict,
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function isStructurallySafe(source) {
|
|
523
|
+
let bracketDepth = 0;
|
|
524
|
+
let braceDepth = 0;
|
|
525
|
+
let inSingle = false;
|
|
526
|
+
let inDouble = false;
|
|
527
|
+
let escaped = false;
|
|
528
|
+
let tripleState = {
|
|
529
|
+
inTripleDouble: false,
|
|
530
|
+
inTripleSingle: false,
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
for (const line of source.split('\n')) {
|
|
534
|
+
tripleState = advanceTripleQuoteState(line, tripleState);
|
|
535
|
+
|
|
536
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
537
|
+
const char = line[index] ?? '';
|
|
538
|
+
|
|
539
|
+
if (!inSingle && !inDouble && char === '#') {
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (char === '"' && !inSingle && !escaped) {
|
|
544
|
+
inDouble = !inDouble;
|
|
545
|
+
} else if (char === "'" && !inDouble && !escaped) {
|
|
546
|
+
inSingle = !inSingle;
|
|
547
|
+
} else if (!inSingle && !inDouble) {
|
|
548
|
+
if (char === '[') {
|
|
549
|
+
bracketDepth += 1;
|
|
550
|
+
} else if (char === ']') {
|
|
551
|
+
bracketDepth -= 1;
|
|
552
|
+
if (bracketDepth < 0) {
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
} else if (char === '{') {
|
|
556
|
+
braceDepth += 1;
|
|
557
|
+
} else if (char === '}') {
|
|
558
|
+
braceDepth -= 1;
|
|
559
|
+
if (braceDepth < 0) {
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
escaped = char === '\\' && !escaped;
|
|
566
|
+
if (char !== '\\') {
|
|
567
|
+
escaped = false;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return (
|
|
573
|
+
!tripleState.inTripleDouble &&
|
|
574
|
+
!tripleState.inTripleSingle &&
|
|
575
|
+
bracketDepth === 0 &&
|
|
576
|
+
braceDepth === 0 &&
|
|
577
|
+
!inSingle &&
|
|
578
|
+
!inDouble
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
try {
|
|
583
|
+
const source = fs.readFileSync(configPath, 'utf8');
|
|
584
|
+
if (!isStructurallySafe(source)) {
|
|
585
|
+
process.exit(1);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const blocks = findAllCodexBlocks(source);
|
|
589
|
+
if (blocks.length !== 1) {
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const parsed = parseCodexBlock(blocks[0]);
|
|
594
|
+
const isConfigured =
|
|
595
|
+
!parsed.hasConflict &&
|
|
596
|
+
parsed.command === expectedCommand &&
|
|
597
|
+
Array.isArray(parsed.args) &&
|
|
598
|
+
parsed.args.length === expectedArgs.length &&
|
|
599
|
+
parsed.args.every((entry, index) => entry === expectedArgs[index]);
|
|
600
|
+
|
|
601
|
+
process.exit(isConfigured ? 0 : 1);
|
|
602
|
+
} catch {
|
|
603
|
+
process.exit(1);
|
|
604
|
+
}
|
|
605
|
+
"@
|
|
606
|
+
return $LASTEXITCODE -eq 0
|
|
607
|
+
}
|
|
608
|
+
finally {
|
|
609
|
+
Remove-Item Env:\MCP_CODEX_CONFIG_PATH -ErrorAction SilentlyContinue
|
|
610
|
+
}
|
|
181
611
|
}
|
|
182
612
|
|
|
183
613
|
# Add MCP server to JSON config file (PowerShell 5.1 compatible — edits JSON via node)
|