bla-agents 1.1.0 → 1.5.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/.agents/skills/blind-llm-agents/SKILL.md +20 -14
- package/DISCLAIMER.md +15 -7
- package/README.md +153 -198
- package/dist/bundler/index.d.ts +19 -2
- package/dist/bundler/index.d.ts.map +1 -1
- package/dist/bundler/index.js +47 -4
- package/dist/bundler/index.js.map +1 -1
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +123 -18
- package/dist/cli.js.map +1 -1
- package/dist/core/config.d.ts +54 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +160 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/coverage-contract.d.ts +97 -0
- package/dist/core/coverage-contract.d.ts.map +1 -0
- package/dist/core/coverage-contract.js +164 -0
- package/dist/core/coverage-contract.js.map +1 -0
- package/dist/core/debug-posture.d.ts +40 -0
- package/dist/core/debug-posture.d.ts.map +1 -0
- package/dist/core/debug-posture.js +197 -0
- package/dist/core/debug-posture.js.map +1 -0
- package/dist/core/detector.d.ts.map +1 -1
- package/dist/core/detector.js +58 -6
- package/dist/core/detector.js.map +1 -1
- package/dist/core/identifiers.d.ts +32 -0
- package/dist/core/identifiers.d.ts.map +1 -0
- package/dist/core/identifiers.js +263 -0
- package/dist/core/identifiers.js.map +1 -0
- package/dist/core/llm-directives.d.ts +12 -0
- package/dist/core/llm-directives.d.ts.map +1 -0
- package/dist/core/llm-directives.js +23 -0
- package/dist/core/llm-directives.js.map +1 -0
- package/dist/core/prng.d.ts +10 -1
- package/dist/core/prng.d.ts.map +1 -1
- package/dist/core/prng.js +27 -2
- package/dist/core/prng.js.map +1 -1
- package/dist/core/release-plan.d.ts +68 -0
- package/dist/core/release-plan.d.ts.map +1 -0
- package/dist/core/release-plan.js +235 -0
- package/dist/core/release-plan.js.map +1 -0
- package/dist/core/sarif.d.ts +53 -0
- package/dist/core/sarif.d.ts.map +1 -0
- package/dist/core/sarif.js +91 -0
- package/dist/core/sarif.js.map +1 -0
- package/dist/core/sqlite-runtime.d.ts +13 -0
- package/dist/core/sqlite-runtime.d.ts.map +1 -1
- package/dist/core/sqlite-runtime.js +38 -8
- package/dist/core/sqlite-runtime.js.map +1 -1
- package/dist/core/types.d.ts +7 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/engine/auditor.d.ts.map +1 -1
- package/dist/engine/auditor.js +251 -8
- package/dist/engine/auditor.js.map +1 -1
- package/dist/engine/benchmark.d.ts +1 -1
- package/dist/engine/benchmark.d.ts.map +1 -1
- package/dist/engine/benchmark.js +121 -41
- package/dist/engine/benchmark.js.map +1 -1
- package/dist/engine/defender.d.ts +1 -0
- package/dist/engine/defender.d.ts.map +1 -1
- package/dist/engine/defender.js +7 -1
- package/dist/engine/defender.js.map +1 -1
- package/dist/engine/harden.d.ts +61 -0
- package/dist/engine/harden.d.ts.map +1 -0
- package/dist/engine/harden.js +292 -0
- package/dist/engine/harden.js.map +1 -0
- package/dist/engine/protector.d.ts.map +1 -1
- package/dist/engine/protector.js +162 -21
- package/dist/engine/protector.js.map +1 -1
- package/dist/identity.d.ts +112 -2
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +112 -2
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +26 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -9
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +59 -152
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +466 -50
- package/dist/mcp/server.js.map +1 -1
- package/dist/setup/clients.d.ts +49 -1
- package/dist/setup/clients.d.ts.map +1 -1
- package/dist/setup/clients.js +1193 -20
- package/dist/setup/clients.js.map +1 -1
- package/dist/setup/doctor.d.ts +58 -14
- package/dist/setup/doctor.d.ts.map +1 -1
- package/dist/setup/doctor.js +316 -22
- package/dist/setup/doctor.js.map +1 -1
- package/dist/setup/setup.d.ts +77 -7
- package/dist/setup/setup.d.ts.map +1 -1
- package/dist/setup/setup.js +591 -62
- package/dist/setup/setup.js.map +1 -1
- package/dist/shields/chaff/chaff-generator.d.ts.map +1 -1
- package/dist/shields/chaff/chaff-generator.js +12 -3
- package/dist/shields/chaff/chaff-generator.js.map +1 -1
- package/dist/shields/deception/architecture-mirage.js +1 -1
- package/dist/shields/deception/architecture-mirage.js.map +1 -1
- package/dist/shields/deception/canary-honeypot.d.ts +33 -0
- package/dist/shields/deception/canary-honeypot.d.ts.map +1 -0
- package/dist/shields/deception/canary-honeypot.js +95 -0
- package/dist/shields/deception/canary-honeypot.js.map +1 -0
- package/dist/shields/deception/cognitive-armor-trigger.d.ts +43 -0
- package/dist/shields/deception/cognitive-armor-trigger.d.ts.map +1 -0
- package/dist/shields/deception/cognitive-armor-trigger.js +204 -0
- package/dist/shields/deception/cognitive-armor-trigger.js.map +1 -0
- package/dist/shields/deception/crash-trace-decoy.js +1 -1
- package/dist/shields/deception/crash-trace-decoy.js.map +1 -1
- package/dist/shields/deception/decompiler-illusion.js +1 -1
- package/dist/shields/deception/decompiler-illusion.js.map +1 -1
- package/dist/shields/deception/diff-noise-injector.js +1 -1
- package/dist/shields/deception/diff-noise-injector.js.map +1 -1
- package/dist/shields/deception/evidence-contract-poison.js +1 -1
- package/dist/shields/deception/evidence-contract-poison.js.map +1 -1
- package/dist/shields/deception/format-payloads.d.ts +21 -0
- package/dist/shields/deception/format-payloads.d.ts.map +1 -0
- package/dist/shields/deception/format-payloads.js +24 -0
- package/dist/shields/deception/format-payloads.js.map +1 -0
- package/dist/shields/deception/index.d.ts +10 -2
- package/dist/shields/deception/index.d.ts.map +1 -1
- package/dist/shields/deception/index.js +20 -3
- package/dist/shields/deception/index.js.map +1 -1
- package/dist/shields/deception/prompt-completion-poison.d.ts +3 -2
- package/dist/shields/deception/prompt-completion-poison.d.ts.map +1 -1
- package/dist/shields/deception/prompt-completion-poison.js +6 -3
- package/dist/shields/deception/prompt-completion-poison.js.map +1 -1
- package/dist/shields/deception/shadow-business-logic.js +4 -4
- package/dist/shields/deception/shadow-business-logic.js.map +1 -1
- package/dist/shields/deception/system-diagnostic-decoy.d.ts +36 -0
- package/dist/shields/deception/system-diagnostic-decoy.d.ts.map +1 -0
- package/dist/shields/deception/system-diagnostic-decoy.js +101 -0
- package/dist/shields/deception/system-diagnostic-decoy.js.map +1 -0
- package/dist/shields/exhaustion/asar-header-amplifier.d.ts +12 -3
- package/dist/shields/exhaustion/asar-header-amplifier.d.ts.map +1 -1
- package/dist/shields/exhaustion/asar-header-amplifier.js +30 -8
- package/dist/shields/exhaustion/asar-header-amplifier.js.map +1 -1
- package/dist/shields/exhaustion/bpe-token-amplifier.js +2 -2
- package/dist/shields/exhaustion/bpe-token-amplifier.js.map +1 -1
- package/dist/shields/exhaustion/callgraph-labyrinth.js +2 -2
- package/dist/shields/exhaustion/callgraph-labyrinth.js.map +1 -1
- package/dist/shields/exhaustion/cdp-shape-budget-breaker.js +1 -1
- package/dist/shields/exhaustion/cdp-shape-budget-breaker.js.map +1 -1
- package/dist/shields/exhaustion/frida-capture-saturation.d.ts +10 -5
- package/dist/shields/exhaustion/frida-capture-saturation.d.ts.map +1 -1
- package/dist/shields/exhaustion/frida-capture-saturation.js +21 -8
- package/dist/shields/exhaustion/frida-capture-saturation.js.map +1 -1
- package/dist/shields/exhaustion/index.d.ts +2 -2
- package/dist/shields/exhaustion/index.d.ts.map +1 -1
- package/dist/shields/exhaustion/index.js +2 -2
- package/dist/shields/exhaustion/index.js.map +1 -1
- package/dist/shields/exhaustion/obligation-flooder.d.ts +17 -5
- package/dist/shields/exhaustion/obligation-flooder.d.ts.map +1 -1
- package/dist/shields/exhaustion/obligation-flooder.js +20 -8
- package/dist/shields/exhaustion/obligation-flooder.js.map +1 -1
- package/dist/shields/exhaustion/rea-budget-matrix.d.ts +8 -2
- package/dist/shields/exhaustion/rea-budget-matrix.d.ts.map +1 -1
- package/dist/shields/exhaustion/rea-budget-matrix.js +139 -16
- package/dist/shields/exhaustion/rea-budget-matrix.js.map +1 -1
- package/dist/shields/exhaustion/regex-worker-tarpit.d.ts.map +1 -1
- package/dist/shields/exhaustion/regex-worker-tarpit.js +2 -1
- package/dist/shields/exhaustion/regex-worker-tarpit.js.map +1 -1
- package/dist/shields/exhaustion/wasm-interface-exhaustion.d.ts +7 -4
- package/dist/shields/exhaustion/wasm-interface-exhaustion.d.ts.map +1 -1
- package/dist/shields/exhaustion/wasm-interface-exhaustion.js +16 -7
- package/dist/shields/exhaustion/wasm-interface-exhaustion.js.map +1 -1
- package/dist/shields/formats/android-dex-xref.d.ts +19 -0
- package/dist/shields/formats/android-dex-xref.d.ts.map +1 -0
- package/dist/shields/formats/android-dex-xref.js +427 -0
- package/dist/shields/formats/android-dex-xref.js.map +1 -0
- package/dist/shields/formats/android-manifest.d.ts +44 -0
- package/dist/shields/formats/android-manifest.d.ts.map +1 -0
- package/dist/shields/formats/android-manifest.js +240 -0
- package/dist/shields/formats/android-manifest.js.map +1 -0
- package/dist/shields/formats/android-r8.d.ts +44 -0
- package/dist/shields/formats/android-r8.d.ts.map +1 -0
- package/dist/shields/formats/android-r8.js +246 -0
- package/dist/shields/formats/android-r8.js.map +1 -0
- package/dist/shields/formats/apple-plist.d.ts +15 -0
- package/dist/shields/formats/apple-plist.d.ts.map +1 -1
- package/dist/shields/formats/apple-plist.js +59 -1
- package/dist/shields/formats/apple-plist.js.map +1 -1
- package/dist/shields/formats/archive.d.ts +27 -6
- package/dist/shields/formats/archive.d.ts.map +1 -1
- package/dist/shields/formats/archive.js +247 -45
- package/dist/shields/formats/archive.js.map +1 -1
- package/dist/shields/formats/asar.d.ts +50 -10
- package/dist/shields/formats/asar.d.ts.map +1 -1
- package/dist/shields/formats/asar.js +265 -39
- package/dist/shields/formats/asar.js.map +1 -1
- package/dist/shields/formats/bun-standalone.d.ts +78 -0
- package/dist/shields/formats/bun-standalone.d.ts.map +1 -0
- package/dist/shields/formats/bun-standalone.js +272 -0
- package/dist/shields/formats/bun-standalone.js.map +1 -0
- package/dist/shields/formats/dotnet-managed.d.ts +9 -0
- package/dist/shields/formats/dotnet-managed.d.ts.map +1 -1
- package/dist/shields/formats/dotnet-managed.js +26 -1
- package/dist/shields/formats/dotnet-managed.js.map +1 -1
- package/dist/shields/formats/electron-fuses.d.ts +63 -8
- package/dist/shields/formats/electron-fuses.d.ts.map +1 -1
- package/dist/shields/formats/electron-fuses.js +297 -65
- package/dist/shields/formats/electron-fuses.js.map +1 -1
- package/dist/shields/formats/erlang-beam.d.ts +34 -0
- package/dist/shields/formats/erlang-beam.d.ts.map +1 -0
- package/dist/shields/formats/erlang-beam.js +319 -0
- package/dist/shields/formats/erlang-beam.js.map +1 -0
- package/dist/shields/formats/evm-bytecode.d.ts +28 -0
- package/dist/shields/formats/evm-bytecode.d.ts.map +1 -0
- package/dist/shields/formats/evm-bytecode.js +124 -0
- package/dist/shields/formats/evm-bytecode.js.map +1 -0
- package/dist/shields/formats/go-flutter-exhaustion.d.ts +55 -0
- package/dist/shields/formats/go-flutter-exhaustion.d.ts.map +1 -0
- package/dist/shields/formats/go-flutter-exhaustion.js +346 -0
- package/dist/shields/formats/go-flutter-exhaustion.js.map +1 -0
- package/dist/shields/formats/gpu-ptx.d.ts +40 -0
- package/dist/shields/formats/gpu-ptx.d.ts.map +1 -0
- package/dist/shields/formats/gpu-ptx.js +62 -0
- package/dist/shields/formats/gpu-ptx.js.map +1 -0
- package/dist/shields/formats/headers.d.ts.map +1 -1
- package/dist/shields/formats/headers.js +54 -14
- package/dist/shields/formats/headers.js.map +1 -1
- package/dist/shields/formats/jvm-bytecode.d.ts +20 -0
- package/dist/shields/formats/jvm-bytecode.d.ts.map +1 -0
- package/dist/shields/formats/jvm-bytecode.js +134 -0
- package/dist/shields/formats/jvm-bytecode.js.map +1 -0
- package/dist/shields/formats/mobile-game.d.ts +4 -0
- package/dist/shields/formats/mobile-game.d.ts.map +1 -1
- package/dist/shields/formats/mobile-game.js +16 -0
- package/dist/shields/formats/mobile-game.js.map +1 -1
- package/dist/shields/formats/model.d.ts +22 -8
- package/dist/shields/formats/model.d.ts.map +1 -1
- package/dist/shields/formats/model.js +231 -59
- package/dist/shields/formats/model.js.map +1 -1
- package/dist/shields/formats/native.d.ts +65 -11
- package/dist/shields/formats/native.d.ts.map +1 -1
- package/dist/shields/formats/native.js +413 -108
- package/dist/shields/formats/native.js.map +1 -1
- package/dist/shields/formats/pcap-capture.d.ts +27 -0
- package/dist/shields/formats/pcap-capture.d.ts.map +1 -0
- package/dist/shields/formats/pcap-capture.js +201 -0
- package/dist/shields/formats/pcap-capture.js.map +1 -0
- package/dist/shields/formats/python.d.ts +22 -1
- package/dist/shields/formats/python.d.ts.map +1 -1
- package/dist/shields/formats/python.js +568 -27
- package/dist/shields/formats/python.js.map +1 -1
- package/dist/shields/formats/sourcemap.d.ts.map +1 -1
- package/dist/shields/formats/sourcemap.js +2 -0
- package/dist/shields/formats/sourcemap.js.map +1 -1
- package/dist/shields/formats/sqlite.d.ts +87 -15
- package/dist/shields/formats/sqlite.d.ts.map +1 -1
- package/dist/shields/formats/sqlite.js +377 -42
- package/dist/shields/formats/sqlite.js.map +1 -1
- package/dist/shields/formats/wasm.d.ts +6 -0
- package/dist/shields/formats/wasm.d.ts.map +1 -1
- package/dist/shields/formats/wasm.js +118 -3
- package/dist/shields/formats/wasm.js.map +1 -1
- package/dist/shields/javascript/ast-shield.d.ts +25 -5
- package/dist/shields/javascript/ast-shield.d.ts.map +1 -1
- package/dist/shields/javascript/ast-shield.js +428 -58
- package/dist/shields/javascript/ast-shield.js.map +1 -1
- package/dist/shields/runtime/memory-diff-shield.d.ts +56 -0
- package/dist/shields/runtime/memory-diff-shield.d.ts.map +1 -0
- package/dist/shields/runtime/memory-diff-shield.js +170 -0
- package/dist/shields/runtime/memory-diff-shield.js.map +1 -0
- package/dist/shields/runtime/runtime-shield.d.ts +12 -16
- package/dist/shields/runtime/runtime-shield.d.ts.map +1 -1
- package/dist/shields/runtime/runtime-shield.js +30 -24
- package/dist/shields/runtime/runtime-shield.js.map +1 -1
- package/dist/shields/tarpit/opaque-predicates.d.ts.map +1 -1
- package/dist/shields/tarpit/opaque-predicates.js +3 -3
- package/dist/shields/tarpit/opaque-predicates.js.map +1 -1
- package/dist/shields/tarpit/tarpit-generator.d.ts +9 -4
- package/dist/shields/tarpit/tarpit-generator.d.ts.map +1 -1
- package/dist/shields/tarpit/tarpit-generator.js +30 -23
- package/dist/shields/tarpit/tarpit-generator.js.map +1 -1
- package/package.json +13 -6
package/dist/setup/clients.js
CHANGED
|
@@ -1,86 +1,1252 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { BLA_IDENTITY } from "../identity.js";
|
|
5
|
+
export const CLIENT_COMPATIBILITY_MATRIX = Object.freeze([
|
|
6
|
+
{
|
|
7
|
+
clientId: "claude_code",
|
|
8
|
+
displayName: "Claude Code",
|
|
9
|
+
format: "json-mcpServers",
|
|
10
|
+
rootKey: "mcpServers",
|
|
11
|
+
configPathTemplate: "~/.claude.json",
|
|
12
|
+
schemaVersion: "claude-code-mcp-v1",
|
|
13
|
+
officialSpecUrl: "https://docs.anthropic.com/en/docs/claude-code/mcp",
|
|
14
|
+
verificationMode: "automated-stdio-smoke",
|
|
15
|
+
verifiedClientVersion: "1.0.x (automated stdio protocol + schema fixture)",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
clientId: "claude_desktop",
|
|
19
|
+
displayName: "Claude Desktop",
|
|
20
|
+
format: "json-mcpServers",
|
|
21
|
+
rootKey: "mcpServers",
|
|
22
|
+
configPathTemplate: "~/Library/Application Support/Claude/claude_desktop_config.json",
|
|
23
|
+
schemaVersion: "claude-desktop-mcp-v1",
|
|
24
|
+
officialSpecUrl: "https://modelcontextprotocol.io/quickstart/user",
|
|
25
|
+
verificationMode: "schema-and-fixture-verified",
|
|
26
|
+
verifiedClientVersion: "0.9.x (GUI client — schema fixture verified, runtime unverified in headless CI)",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
clientId: "cursor",
|
|
30
|
+
displayName: "Cursor",
|
|
31
|
+
format: "json-mcpServers",
|
|
32
|
+
rootKey: "mcpServers",
|
|
33
|
+
configPathTemplate: "~/.cursor/mcp.json",
|
|
34
|
+
schemaVersion: "cursor-mcp-v1",
|
|
35
|
+
officialSpecUrl: "https://docs.cursor.com/context/model-context-protocol",
|
|
36
|
+
verificationMode: "schema-and-fixture-verified",
|
|
37
|
+
verifiedClientVersion: "0.48.x (GUI client — schema fixture verified, runtime unverified in headless CI)",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
clientId: "codex",
|
|
41
|
+
displayName: "Codex CLI",
|
|
42
|
+
format: "toml-codex",
|
|
43
|
+
rootKey: "mcp_servers",
|
|
44
|
+
configPathTemplate: "~/.codex/config.toml",
|
|
45
|
+
schemaVersion: "codex-config-toml-v1",
|
|
46
|
+
officialSpecUrl: "https://developers.openai.com/codex/mcp/",
|
|
47
|
+
verificationMode: "automated-stdio-smoke",
|
|
48
|
+
verifiedClientVersion: "0.1.x (TOML [mcp_servers.bla] spec + automated stdio smoke)",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
clientId: "gemini_cli",
|
|
52
|
+
displayName: "Gemini CLI",
|
|
53
|
+
format: "json-mcpServers",
|
|
54
|
+
rootKey: "mcpServers",
|
|
55
|
+
configPathTemplate: "~/.gemini/settings.json",
|
|
56
|
+
schemaVersion: "gemini-cli-settings-v1",
|
|
57
|
+
officialSpecUrl: "https://github.com/google-gemini/gemini-cli",
|
|
58
|
+
verificationMode: "automated-stdio-smoke",
|
|
59
|
+
verifiedClientVersion: "0.1.x (settings.json mcpServers + automated stdio smoke)",
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
clientId: "antigravity",
|
|
63
|
+
displayName: "Google Antigravity",
|
|
64
|
+
format: "json-mcpServers",
|
|
65
|
+
rootKey: "mcpServers",
|
|
66
|
+
configPathTemplate: "~/.gemini/antigravity/mcp_config.json",
|
|
67
|
+
schemaVersion: "antigravity-mcp-v1",
|
|
68
|
+
officialSpecUrl: "https://idx.dev",
|
|
69
|
+
verificationMode: "schema-and-fixture-verified",
|
|
70
|
+
verifiedClientVersion: "2.0.x (mcpServers schema fixture verified)",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
clientId: "windsurf",
|
|
74
|
+
displayName: "Windsurf",
|
|
75
|
+
format: "json-mcpServers",
|
|
76
|
+
rootKey: "mcpServers",
|
|
77
|
+
configPathTemplate: "~/.codeium/windsurf/mcp_config.json",
|
|
78
|
+
schemaVersion: "windsurf-mcp-v1",
|
|
79
|
+
officialSpecUrl: "https://docs.windsurf.com/windsurf/mcp",
|
|
80
|
+
verificationMode: "schema-and-fixture-verified",
|
|
81
|
+
verifiedClientVersion: "1.6.x (GUI client — schema fixture verified, runtime unverified in headless CI)",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
clientId: "vscode",
|
|
85
|
+
displayName: "VS Code",
|
|
86
|
+
format: "json-vscode",
|
|
87
|
+
rootKey: "servers",
|
|
88
|
+
configPathTemplate: "<UserConfig>/Code/User/mcp.json",
|
|
89
|
+
schemaVersion: "vscode-mcp-servers-v1",
|
|
90
|
+
officialSpecUrl: "https://code.visualstudio.com/docs/agent-customization/mcp-servers",
|
|
91
|
+
verificationMode: "schema-and-fixture-verified",
|
|
92
|
+
verifiedClientVersion: "1.99+ (user-profile mcp.json 'servers' schema fixture verified; GUI runtime unverified in CI)",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
clientId: "opencode",
|
|
96
|
+
displayName: "OpenCode",
|
|
97
|
+
format: "json-opencode",
|
|
98
|
+
rootKey: "mcp",
|
|
99
|
+
configPathTemplate: "~/.config/opencode/opencode.json",
|
|
100
|
+
schemaVersion: "opencode-mcp-local-v1",
|
|
101
|
+
officialSpecUrl: "https://opencode.ai/docs/mcp-servers/",
|
|
102
|
+
verificationMode: "automated-stdio-smoke",
|
|
103
|
+
verifiedClientVersion: "0.1.x (opencode.json mcp.bla local command array + automated stdio smoke)",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
clientId: "copilot_cli",
|
|
107
|
+
displayName: "GitHub Copilot CLI",
|
|
108
|
+
format: "json-mcpServers",
|
|
109
|
+
rootKey: "mcpServers",
|
|
110
|
+
configPathTemplate: "~/.copilot/mcp-config.json",
|
|
111
|
+
schemaVersion: "copilot-cli-mcp-v1",
|
|
112
|
+
officialSpecUrl: "https://docs.github.com/en/copilot",
|
|
113
|
+
verificationMode: "schema-and-fixture-verified",
|
|
114
|
+
verifiedClientVersion: "1.0.x (mcpServers schema fixture verified)",
|
|
115
|
+
},
|
|
116
|
+
]);
|
|
117
|
+
const CLIENT_ID_ALIASES = Object.freeze({
|
|
118
|
+
claude_code: "claude_code",
|
|
119
|
+
"claude-code": "claude_code",
|
|
120
|
+
claude_desktop: "claude_desktop",
|
|
121
|
+
"claude-desktop": "claude_desktop",
|
|
122
|
+
cursor: "cursor",
|
|
123
|
+
codex: "codex",
|
|
124
|
+
gemini_cli: "gemini_cli",
|
|
125
|
+
"gemini-cli": "gemini_cli",
|
|
126
|
+
windsurf: "windsurf",
|
|
127
|
+
antigravity: "antigravity",
|
|
128
|
+
vscode: "vscode",
|
|
129
|
+
"vs-code": "vscode",
|
|
130
|
+
opencode: "opencode",
|
|
131
|
+
copilot_cli: "copilot_cli",
|
|
132
|
+
"copilot-cli": "copilot_cli",
|
|
133
|
+
});
|
|
134
|
+
export function normalizeClientId(id) {
|
|
135
|
+
return CLIENT_ID_ALIASES[id.trim().toLowerCase()] ?? null;
|
|
136
|
+
}
|
|
137
|
+
function isPlainObject(value) {
|
|
138
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Strip inline `#` comments outside quoted strings on a single TOML line.
|
|
142
|
+
*/
|
|
143
|
+
function stripTomlInlineComment(line) {
|
|
144
|
+
let inDouble = false;
|
|
145
|
+
let inSingle = false;
|
|
146
|
+
let escaped = false;
|
|
147
|
+
for (let i = 0; i < line.length; i++) {
|
|
148
|
+
const ch = line[i];
|
|
149
|
+
if (escaped) {
|
|
150
|
+
escaped = false;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (inDouble) {
|
|
154
|
+
if (ch === "\\") {
|
|
155
|
+
escaped = true;
|
|
156
|
+
}
|
|
157
|
+
else if (ch === '"') {
|
|
158
|
+
inDouble = false;
|
|
159
|
+
}
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (inSingle) {
|
|
163
|
+
if (ch === "'") {
|
|
164
|
+
inSingle = false;
|
|
165
|
+
}
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (ch === '"') {
|
|
169
|
+
inDouble = true;
|
|
170
|
+
}
|
|
171
|
+
else if (ch === "'") {
|
|
172
|
+
inSingle = true;
|
|
173
|
+
}
|
|
174
|
+
else if (ch === "#") {
|
|
175
|
+
return { code: line.slice(0, i).trim(), unclosedQuote: false };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return { code: line.trim(), unclosedQuote: inDouble || inSingle };
|
|
179
|
+
}
|
|
180
|
+
function parseTomlStringLiteral(raw) {
|
|
181
|
+
const trimmed = raw.trim();
|
|
182
|
+
if (trimmed.length < 2)
|
|
183
|
+
return { ok: false };
|
|
184
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
185
|
+
try {
|
|
186
|
+
const parsed = JSON.parse(trimmed);
|
|
187
|
+
if (typeof parsed === "string") {
|
|
188
|
+
return { ok: true, value: parsed };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return { ok: false };
|
|
193
|
+
}
|
|
194
|
+
return { ok: false };
|
|
195
|
+
}
|
|
196
|
+
if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
197
|
+
const inner = trimmed.slice(1, -1);
|
|
198
|
+
if (inner.includes("'"))
|
|
199
|
+
return { ok: false };
|
|
200
|
+
return { ok: true, value: inner };
|
|
201
|
+
}
|
|
202
|
+
return { ok: false };
|
|
203
|
+
}
|
|
204
|
+
function parseTomlStringArray(raw) {
|
|
205
|
+
const trimmed = raw.trim();
|
|
206
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) {
|
|
207
|
+
return { ok: false };
|
|
208
|
+
}
|
|
209
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
210
|
+
if (inner.length === 0) {
|
|
211
|
+
return { ok: true, values: [] };
|
|
212
|
+
}
|
|
213
|
+
const items = [];
|
|
214
|
+
let current = "";
|
|
215
|
+
let inDouble = false;
|
|
216
|
+
let inSingle = false;
|
|
217
|
+
let escaped = false;
|
|
218
|
+
let depth = 0;
|
|
219
|
+
for (let i = 0; i < inner.length; i++) {
|
|
220
|
+
const ch = inner[i];
|
|
221
|
+
if (escaped) {
|
|
222
|
+
current += ch;
|
|
223
|
+
escaped = false;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (inDouble) {
|
|
227
|
+
current += ch;
|
|
228
|
+
if (ch === "\\")
|
|
229
|
+
escaped = true;
|
|
230
|
+
else if (ch === '"')
|
|
231
|
+
inDouble = false;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (inSingle) {
|
|
235
|
+
current += ch;
|
|
236
|
+
if (ch === "'")
|
|
237
|
+
inSingle = false;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (ch === '"') {
|
|
241
|
+
inDouble = true;
|
|
242
|
+
current += ch;
|
|
243
|
+
}
|
|
244
|
+
else if (ch === "'") {
|
|
245
|
+
inSingle = true;
|
|
246
|
+
current += ch;
|
|
247
|
+
}
|
|
248
|
+
else if (ch === "[") {
|
|
249
|
+
depth++;
|
|
250
|
+
current += ch;
|
|
251
|
+
}
|
|
252
|
+
else if (ch === "]") {
|
|
253
|
+
depth--;
|
|
254
|
+
if (depth < 0)
|
|
255
|
+
return { ok: false };
|
|
256
|
+
current += ch;
|
|
257
|
+
}
|
|
258
|
+
else if (ch === "," && depth === 0) {
|
|
259
|
+
const parsedItem = parseTomlStringLiteral(current);
|
|
260
|
+
if (!parsedItem.ok)
|
|
261
|
+
return { ok: false };
|
|
262
|
+
items.push(parsedItem.value);
|
|
263
|
+
current = "";
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
current += ch;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (inDouble || inSingle || depth !== 0) {
|
|
270
|
+
return { ok: false };
|
|
271
|
+
}
|
|
272
|
+
if (current.trim().length > 0) {
|
|
273
|
+
const parsedItem = parseTomlStringLiteral(current);
|
|
274
|
+
if (!parsedItem.ok)
|
|
275
|
+
return { ok: false };
|
|
276
|
+
items.push(parsedItem.value);
|
|
277
|
+
}
|
|
278
|
+
return { ok: true, values: items };
|
|
279
|
+
}
|
|
280
|
+
function inspectCodexTomlDocument(rawToml) {
|
|
281
|
+
const lines = rawToml.split(/\r?\n/);
|
|
282
|
+
const sections = [];
|
|
283
|
+
const topLevelAssignments = new Map();
|
|
284
|
+
let currentSection = null;
|
|
285
|
+
let pendingKey = null;
|
|
286
|
+
let pendingArrayBuffer = "";
|
|
287
|
+
let bracketBalance = 0;
|
|
288
|
+
const countArrayBracketsOutsideQuotes = (code) => {
|
|
289
|
+
let delta = 0;
|
|
290
|
+
let inDouble = false;
|
|
291
|
+
let inSingle = false;
|
|
292
|
+
let escaped = false;
|
|
293
|
+
for (let i = 0; i < code.length; i++) {
|
|
294
|
+
const ch = code[i];
|
|
295
|
+
if (escaped) {
|
|
296
|
+
escaped = false;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (inDouble) {
|
|
300
|
+
if (ch === "\\")
|
|
301
|
+
escaped = true;
|
|
302
|
+
else if (ch === '"')
|
|
303
|
+
inDouble = false;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (inSingle) {
|
|
307
|
+
if (ch === "'")
|
|
308
|
+
inSingle = false;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (ch === '"')
|
|
312
|
+
inDouble = true;
|
|
313
|
+
else if (ch === "'")
|
|
314
|
+
inSingle = true;
|
|
315
|
+
else if (ch === "[")
|
|
316
|
+
delta++;
|
|
317
|
+
else if (ch === "]")
|
|
318
|
+
delta--;
|
|
319
|
+
}
|
|
320
|
+
return { delta, valid: !inDouble && !inSingle };
|
|
321
|
+
};
|
|
322
|
+
for (let idx = 0; idx < lines.length; idx++) {
|
|
323
|
+
const rawLine = lines[idx];
|
|
324
|
+
const stripped = stripTomlInlineComment(rawLine);
|
|
325
|
+
if (stripped.unclosedQuote) {
|
|
326
|
+
return { ok: false, error: `Malformed TOML at line ${idx + 1}: unclosed string quote` };
|
|
327
|
+
}
|
|
328
|
+
const code = stripped.code;
|
|
329
|
+
if (code.length === 0)
|
|
330
|
+
continue;
|
|
331
|
+
if (pendingKey !== null) {
|
|
332
|
+
const bracketCheck = countArrayBracketsOutsideQuotes(code);
|
|
333
|
+
if (!bracketCheck.valid) {
|
|
334
|
+
return { ok: false, error: `Malformed TOML array at line ${idx + 1}` };
|
|
335
|
+
}
|
|
336
|
+
pendingArrayBuffer += " " + code;
|
|
337
|
+
bracketBalance += bracketCheck.delta;
|
|
338
|
+
if (bracketBalance < 0) {
|
|
339
|
+
return { ok: false, error: `Malformed TOML at line ${idx + 1}: unexpected closing bracket` };
|
|
340
|
+
}
|
|
341
|
+
if (bracketBalance === 0) {
|
|
342
|
+
const targetMap = currentSection ? currentSection.assignments : topLevelAssignments;
|
|
343
|
+
targetMap.set(pendingKey, pendingArrayBuffer.trim());
|
|
344
|
+
pendingKey = null;
|
|
345
|
+
pendingArrayBuffer = "";
|
|
346
|
+
}
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (code.startsWith("[")) {
|
|
350
|
+
if (!code.endsWith("]")) {
|
|
351
|
+
return { ok: false, error: `Malformed TOML table header at line ${idx + 1}: '${code}'` };
|
|
352
|
+
}
|
|
353
|
+
const isArrayOfTables = code.startsWith("[[") && code.endsWith("]]");
|
|
354
|
+
const tableName = (isArrayOfTables ? code.slice(2, -2) : code.slice(1, -1)).trim();
|
|
355
|
+
if (tableName.length === 0 || tableName.includes("[") || tableName.includes("]")) {
|
|
356
|
+
return { ok: false, error: `Invalid TOML table header at line ${idx + 1}: '${code}'` };
|
|
357
|
+
}
|
|
358
|
+
if (currentSection) {
|
|
359
|
+
currentSection.endLineIndex = idx;
|
|
360
|
+
}
|
|
361
|
+
currentSection = {
|
|
362
|
+
name: tableName,
|
|
363
|
+
headerLineIndex: idx,
|
|
364
|
+
endLineIndex: lines.length,
|
|
365
|
+
assignments: new Map(),
|
|
366
|
+
};
|
|
367
|
+
sections.push(currentSection);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const eqIdx = code.indexOf("=");
|
|
371
|
+
if (eqIdx <= 0) {
|
|
372
|
+
return { ok: false, error: `Malformed TOML statement at line ${idx + 1}: '${code}'` };
|
|
373
|
+
}
|
|
374
|
+
const key = code.slice(0, eqIdx).trim();
|
|
375
|
+
const val = code.slice(eqIdx + 1).trim();
|
|
376
|
+
if (!/^[A-Za-z0-9_.-]+$/.test(key) && !(key.startsWith('"') && key.endsWith('"'))) {
|
|
377
|
+
return { ok: false, error: `Invalid TOML key '${key}' at line ${idx + 1}` };
|
|
378
|
+
}
|
|
379
|
+
if (val.length === 0) {
|
|
380
|
+
return { ok: false, error: `Empty TOML value for key '${key}' at line ${idx + 1}` };
|
|
381
|
+
}
|
|
382
|
+
if (val.startsWith("[")) {
|
|
383
|
+
const bracketCheck = countArrayBracketsOutsideQuotes(val);
|
|
384
|
+
if (!bracketCheck.valid || bracketCheck.delta < 0) {
|
|
385
|
+
return { ok: false, error: `Malformed TOML array for key '${key}' at line ${idx + 1}` };
|
|
386
|
+
}
|
|
387
|
+
if (bracketCheck.delta > 0) {
|
|
388
|
+
pendingKey = key;
|
|
389
|
+
pendingArrayBuffer = val;
|
|
390
|
+
bracketBalance = bracketCheck.delta;
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
else if (val.startsWith("{")) {
|
|
395
|
+
if (!val.endsWith("}")) {
|
|
396
|
+
return { ok: false, error: `Unclosed TOML inline table for key '${key}' at line ${idx + 1}` };
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
else if (val.startsWith('"') || val.startsWith("'")) {
|
|
400
|
+
const parsedStr = parseTomlStringLiteral(val);
|
|
401
|
+
if (!parsedStr.ok) {
|
|
402
|
+
return { ok: false, error: `Invalid TOML string literal for key '${key}' at line ${idx + 1}` };
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
else if (val !== "true" &&
|
|
406
|
+
val !== "false" &&
|
|
407
|
+
!/^[+-]?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(val) &&
|
|
408
|
+
!/^\d{4}-\d{2}-\d{2}/.test(val)) {
|
|
409
|
+
return { ok: false, error: `Invalid TOML literal '${val}' for key '${key}' at line ${idx + 1}` };
|
|
410
|
+
}
|
|
411
|
+
const targetMap = currentSection ? currentSection.assignments : topLevelAssignments;
|
|
412
|
+
targetMap.set(key, val);
|
|
413
|
+
}
|
|
414
|
+
if (pendingKey !== null || bracketBalance !== 0) {
|
|
415
|
+
return { ok: false, error: `Malformed TOML: unclosed multiline array for key '${pendingKey}'` };
|
|
416
|
+
}
|
|
417
|
+
// Schema check: top-level `mcp_servers` cannot be a scalar or array
|
|
418
|
+
const topLevelMcpServers = topLevelAssignments.get("mcp_servers");
|
|
419
|
+
if (topLevelMcpServers !== undefined && !topLevelMcpServers.startsWith("{")) {
|
|
420
|
+
return {
|
|
421
|
+
ok: false,
|
|
422
|
+
error: "Invalid Codex config.toml schema: 'mcp_servers' must be a table, not a scalar or array",
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
ok: true,
|
|
427
|
+
lines,
|
|
428
|
+
sections,
|
|
429
|
+
topLevelAssignments,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
export function readCodexTomlMcp(rawToml, serverName = BLA_IDENTITY.mcpServerName) {
|
|
433
|
+
const inspected = inspectCodexTomlDocument(rawToml);
|
|
434
|
+
if (!inspected.ok) {
|
|
435
|
+
return {
|
|
436
|
+
status: "malformed",
|
|
437
|
+
entry: null,
|
|
438
|
+
error: inspected.error,
|
|
439
|
+
otherServersCount: 0,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
const targetTable = `mcp_servers.${serverName}`;
|
|
443
|
+
let otherServersCount = 0;
|
|
444
|
+
let targetSection;
|
|
445
|
+
for (const sec of inspected.sections) {
|
|
446
|
+
if (sec.name === targetTable) {
|
|
447
|
+
targetSection = sec;
|
|
448
|
+
}
|
|
449
|
+
else if (sec.name.startsWith("mcp_servers.")) {
|
|
450
|
+
const remainder = sec.name.slice("mcp_servers.".length);
|
|
451
|
+
if (remainder.length > 0 && !remainder.includes(".")) {
|
|
452
|
+
otherServersCount++;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else if (sec.name === "mcp_servers") {
|
|
456
|
+
const inlineBla = sec.assignments.get(serverName);
|
|
457
|
+
if (inlineBla !== undefined && !inlineBla.startsWith("{")) {
|
|
458
|
+
return {
|
|
459
|
+
status: "malformed",
|
|
460
|
+
entry: null,
|
|
461
|
+
error: `Invalid Codex config.toml schema: '[mcp_servers].${serverName}' must be a table`,
|
|
462
|
+
otherServersCount,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (!targetSection) {
|
|
468
|
+
return {
|
|
469
|
+
status: "not_configured",
|
|
470
|
+
entry: null,
|
|
471
|
+
otherServersCount,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
const rawCommand = targetSection.assignments.get("command");
|
|
475
|
+
if (rawCommand === undefined) {
|
|
476
|
+
return {
|
|
477
|
+
status: "invalid_entry",
|
|
478
|
+
entry: null,
|
|
479
|
+
error: `Codex [${targetTable}] is missing required 'command' string`,
|
|
480
|
+
otherServersCount,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
const parsedCommand = parseTomlStringLiteral(rawCommand);
|
|
484
|
+
if (!parsedCommand.ok || parsedCommand.value.trim().length === 0) {
|
|
485
|
+
return {
|
|
486
|
+
status: "invalid_entry",
|
|
487
|
+
entry: null,
|
|
488
|
+
error: `Codex [${targetTable}].command must be a non-empty string`,
|
|
489
|
+
otherServersCount,
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
let parsedArgs = [];
|
|
493
|
+
const rawArgs = targetSection.assignments.get("args");
|
|
494
|
+
if (rawArgs !== undefined) {
|
|
495
|
+
const arrRes = parseTomlStringArray(rawArgs);
|
|
496
|
+
if (!arrRes.ok) {
|
|
497
|
+
return {
|
|
498
|
+
status: "invalid_entry",
|
|
499
|
+
entry: null,
|
|
500
|
+
error: `Codex [${targetTable}].args must be an array of strings`,
|
|
501
|
+
otherServersCount,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
parsedArgs = arrRes.values;
|
|
505
|
+
}
|
|
506
|
+
const rawEnabled = targetSection.assignments.get("enabled");
|
|
507
|
+
if (rawEnabled === "false") {
|
|
508
|
+
return {
|
|
509
|
+
status: "invalid_entry",
|
|
510
|
+
entry: null,
|
|
511
|
+
error: `Codex [${targetTable}] is explicitly disabled (enabled = false)`,
|
|
512
|
+
otherServersCount,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
status: "valid",
|
|
517
|
+
entry: {
|
|
518
|
+
command: parsedCommand.value,
|
|
519
|
+
args: parsedArgs,
|
|
520
|
+
},
|
|
521
|
+
rawEntry: Object.fromEntries(targetSection.assignments.entries()),
|
|
522
|
+
otherServersCount,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
export function writeCodexTomlMcp(rawToml, serverName, reg) {
|
|
526
|
+
const existing = rawToml ?? "";
|
|
527
|
+
if (existing.trim().length > 0) {
|
|
528
|
+
const inspected = inspectCodexTomlDocument(existing);
|
|
529
|
+
if (!inspected.ok) {
|
|
530
|
+
throw new Error(inspected.error);
|
|
531
|
+
}
|
|
532
|
+
const targetTable = `mcp_servers.${serverName}`;
|
|
533
|
+
for (const sec of inspected.sections) {
|
|
534
|
+
if (sec.name === "mcp_servers") {
|
|
535
|
+
const inlineVal = sec.assignments.get(serverName);
|
|
536
|
+
if (inlineVal !== undefined && !inlineVal.startsWith("{")) {
|
|
537
|
+
throw new Error(`Invalid Codex config.toml schema: '[mcp_servers].${serverName}' is not a table`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
const formattedSectionLines = [
|
|
542
|
+
`[${targetTable}]`,
|
|
543
|
+
`command = ${JSON.stringify(reg.command)}`,
|
|
544
|
+
`args = [${reg.args.map((a) => JSON.stringify(a)).join(", ")}]`,
|
|
545
|
+
];
|
|
546
|
+
const existingSection = inspected.sections.find((s) => s.name === targetTable);
|
|
547
|
+
if (existingSection) {
|
|
548
|
+
// Preserve trailing comments before the next section header
|
|
549
|
+
let replaceEnd = existingSection.endLineIndex;
|
|
550
|
+
while (replaceEnd > existingSection.headerLineIndex + 1 &&
|
|
551
|
+
(inspected.lines[replaceEnd - 1].trim() === "" ||
|
|
552
|
+
inspected.lines[replaceEnd - 1].trim().startsWith("#"))) {
|
|
553
|
+
if (inspected.lines[replaceEnd - 1].trim().startsWith("#")) {
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
replaceEnd--;
|
|
557
|
+
}
|
|
558
|
+
const before = inspected.lines.slice(0, existingSection.headerLineIndex);
|
|
559
|
+
const after = inspected.lines.slice(replaceEnd);
|
|
560
|
+
const combined = [...before, ...formattedSectionLines, ...(after.length > 0 ? ["", ...after] : [])];
|
|
561
|
+
return combined.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
562
|
+
}
|
|
563
|
+
const trimmedExisting = existing.trimEnd();
|
|
564
|
+
return `${trimmedExisting}\n\n${formattedSectionLines.join("\n")}\n`;
|
|
565
|
+
}
|
|
566
|
+
return [
|
|
567
|
+
`[mcp_servers.${serverName}]`,
|
|
568
|
+
`command = ${JSON.stringify(reg.command)}`,
|
|
569
|
+
`args = [${reg.args.map((a) => JSON.stringify(a)).join(", ")}]`,
|
|
570
|
+
"",
|
|
571
|
+
].join("\n");
|
|
572
|
+
}
|
|
573
|
+
export function removeCodexTomlMcp(rawToml, serverName = BLA_IDENTITY.mcpServerName) {
|
|
574
|
+
const inspected = inspectCodexTomlDocument(rawToml);
|
|
575
|
+
if (!inspected.ok) {
|
|
576
|
+
throw new Error(inspected.error);
|
|
577
|
+
}
|
|
578
|
+
const targetTable = `mcp_servers.${serverName}`;
|
|
579
|
+
const existingSection = inspected.sections.find((s) => s.name === targetTable);
|
|
580
|
+
if (!existingSection) {
|
|
581
|
+
return { content: rawToml, removed: false };
|
|
582
|
+
}
|
|
583
|
+
const before = inspected.lines.slice(0, existingSection.headerLineIndex);
|
|
584
|
+
const after = inspected.lines.slice(existingSection.endLineIndex);
|
|
585
|
+
const joined = [...before, ...after].join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
586
|
+
return {
|
|
587
|
+
content: joined.length > 0 ? `${joined}\n` : "",
|
|
588
|
+
removed: true,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function createJsonMcpServersAdapter() {
|
|
592
|
+
return {
|
|
593
|
+
format: "json-mcpServers",
|
|
594
|
+
readServer(rawContent, serverName = BLA_IDENTITY.mcpServerName) {
|
|
595
|
+
let parsed;
|
|
596
|
+
try {
|
|
597
|
+
parsed = JSON.parse(rawContent);
|
|
598
|
+
}
|
|
599
|
+
catch (err) {
|
|
600
|
+
return {
|
|
601
|
+
status: "malformed",
|
|
602
|
+
entry: null,
|
|
603
|
+
error: `Malformed JSON configuration: ${err instanceof Error ? err.message : String(err)}`,
|
|
604
|
+
otherServersCount: 0,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
if (!isPlainObject(parsed)) {
|
|
608
|
+
return {
|
|
609
|
+
status: "malformed",
|
|
610
|
+
entry: null,
|
|
611
|
+
error: "Invalid configuration schema: root value must be a JSON object",
|
|
612
|
+
otherServersCount: 0,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
if (parsed.mcpServers === undefined) {
|
|
616
|
+
return {
|
|
617
|
+
status: "not_configured",
|
|
618
|
+
entry: null,
|
|
619
|
+
otherServersCount: 0,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
if (!isPlainObject(parsed.mcpServers)) {
|
|
623
|
+
return {
|
|
624
|
+
status: "malformed",
|
|
625
|
+
entry: null,
|
|
626
|
+
error: "Invalid configuration schema: 'mcpServers' must be a JSON object",
|
|
627
|
+
otherServersCount: 0,
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
const servers = parsed.mcpServers;
|
|
631
|
+
const otherServersCount = Object.keys(servers).filter((k) => k !== serverName).length;
|
|
632
|
+
if (!(serverName in servers)) {
|
|
633
|
+
return {
|
|
634
|
+
status: "not_configured",
|
|
635
|
+
entry: null,
|
|
636
|
+
otherServersCount,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
const rawEntry = servers[serverName];
|
|
640
|
+
if (!isPlainObject(rawEntry)) {
|
|
641
|
+
return {
|
|
642
|
+
status: "invalid_entry",
|
|
643
|
+
entry: null,
|
|
644
|
+
rawEntry,
|
|
645
|
+
error: `Invalid MCP server entry '${serverName}': must be a JSON object`,
|
|
646
|
+
otherServersCount,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
if (typeof rawEntry.command !== "string" || rawEntry.command.trim().length === 0) {
|
|
650
|
+
return {
|
|
651
|
+
status: "invalid_entry",
|
|
652
|
+
entry: null,
|
|
653
|
+
rawEntry,
|
|
654
|
+
error: `Invalid MCP server entry '${serverName}': 'command' must be a non-empty string`,
|
|
655
|
+
otherServersCount,
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
let args = [];
|
|
659
|
+
if (rawEntry.args !== undefined) {
|
|
660
|
+
if (!Array.isArray(rawEntry.args) || !rawEntry.args.every((a) => typeof a === "string")) {
|
|
661
|
+
return {
|
|
662
|
+
status: "invalid_entry",
|
|
663
|
+
entry: null,
|
|
664
|
+
rawEntry,
|
|
665
|
+
error: `Invalid MCP server entry '${serverName}': 'args' must be an array of strings`,
|
|
666
|
+
otherServersCount,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
args = [...rawEntry.args];
|
|
670
|
+
}
|
|
671
|
+
if (rawEntry.disabled === true || rawEntry.enabled === false) {
|
|
672
|
+
return {
|
|
673
|
+
status: "invalid_entry",
|
|
674
|
+
entry: null,
|
|
675
|
+
rawEntry,
|
|
676
|
+
error: `MCP server entry '${serverName}' is disabled`,
|
|
677
|
+
otherServersCount,
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
return {
|
|
681
|
+
status: "valid",
|
|
682
|
+
entry: {
|
|
683
|
+
command: rawEntry.command,
|
|
684
|
+
args,
|
|
685
|
+
},
|
|
686
|
+
rawEntry,
|
|
687
|
+
otherServersCount,
|
|
688
|
+
};
|
|
689
|
+
},
|
|
690
|
+
upsertServer(rawContent, serverName, reg) {
|
|
691
|
+
let root = {};
|
|
692
|
+
if (rawContent !== null && rawContent.trim().length > 0) {
|
|
693
|
+
let parsed;
|
|
694
|
+
try {
|
|
695
|
+
parsed = JSON.parse(rawContent);
|
|
696
|
+
}
|
|
697
|
+
catch (err) {
|
|
698
|
+
throw new Error(`Malformed JSON configuration: ${err instanceof Error ? err.message : String(err)}`);
|
|
699
|
+
}
|
|
700
|
+
if (!isPlainObject(parsed)) {
|
|
701
|
+
throw new Error("Invalid configuration schema: root value must be a JSON object");
|
|
702
|
+
}
|
|
703
|
+
if (parsed.mcpServers !== undefined && !isPlainObject(parsed.mcpServers)) {
|
|
704
|
+
throw new Error("Invalid configuration schema: 'mcpServers' must be a JSON object");
|
|
705
|
+
}
|
|
706
|
+
root = { ...parsed };
|
|
707
|
+
}
|
|
708
|
+
const existingServers = isPlainObject(root.mcpServers) ? { ...root.mcpServers } : {};
|
|
709
|
+
existingServers[serverName] = {
|
|
710
|
+
command: reg.command,
|
|
711
|
+
args: [...reg.args],
|
|
712
|
+
};
|
|
713
|
+
root.mcpServers = existingServers;
|
|
714
|
+
return JSON.stringify(root, null, 2) + "\n";
|
|
715
|
+
},
|
|
716
|
+
removeServer(rawContent, serverName) {
|
|
717
|
+
let parsed;
|
|
718
|
+
try {
|
|
719
|
+
parsed = JSON.parse(rawContent);
|
|
720
|
+
}
|
|
721
|
+
catch (err) {
|
|
722
|
+
throw new Error(`Malformed JSON configuration: ${err instanceof Error ? err.message : String(err)}`);
|
|
723
|
+
}
|
|
724
|
+
if (!isPlainObject(parsed)) {
|
|
725
|
+
throw new Error("Invalid configuration schema: root value must be a JSON object");
|
|
726
|
+
}
|
|
727
|
+
if (parsed.mcpServers !== undefined && !isPlainObject(parsed.mcpServers)) {
|
|
728
|
+
throw new Error("Invalid configuration schema: 'mcpServers' must be a JSON object");
|
|
729
|
+
}
|
|
730
|
+
if (!isPlainObject(parsed.mcpServers) || !(serverName in parsed.mcpServers)) {
|
|
731
|
+
return { content: rawContent, removed: false };
|
|
732
|
+
}
|
|
733
|
+
const nextServers = { ...parsed.mcpServers };
|
|
734
|
+
delete nextServers[serverName];
|
|
735
|
+
const nextRoot = { ...parsed, mcpServers: nextServers };
|
|
736
|
+
return {
|
|
737
|
+
content: JSON.stringify(nextRoot, null, 2) + "\n",
|
|
738
|
+
removed: true,
|
|
739
|
+
};
|
|
740
|
+
},
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
function createJsonVscodeAdapter() {
|
|
744
|
+
return {
|
|
745
|
+
format: "json-vscode",
|
|
746
|
+
readServer(rawContent, serverName = BLA_IDENTITY.mcpServerName) {
|
|
747
|
+
let parsed;
|
|
748
|
+
try {
|
|
749
|
+
parsed = JSON.parse(rawContent);
|
|
750
|
+
}
|
|
751
|
+
catch (err) {
|
|
752
|
+
return {
|
|
753
|
+
status: "malformed",
|
|
754
|
+
entry: null,
|
|
755
|
+
error: `Malformed VS Code mcp.json: ${err instanceof Error ? err.message : String(err)}`,
|
|
756
|
+
otherServersCount: 0,
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
if (!isPlainObject(parsed)) {
|
|
760
|
+
return {
|
|
761
|
+
status: "malformed",
|
|
762
|
+
entry: null,
|
|
763
|
+
error: "Invalid VS Code mcp.json schema: root value must be a JSON object",
|
|
764
|
+
otherServersCount: 0,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
if (parsed.servers === undefined) {
|
|
768
|
+
if ("mcpServers" in parsed) {
|
|
769
|
+
return {
|
|
770
|
+
status: "malformed",
|
|
771
|
+
entry: null,
|
|
772
|
+
error: "Invalid VS Code user-profile mcp.json schema: expected root key 'servers', found 'mcpServers'",
|
|
773
|
+
otherServersCount: 0,
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
return {
|
|
777
|
+
status: "not_configured",
|
|
778
|
+
entry: null,
|
|
779
|
+
otherServersCount: 0,
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
if (!isPlainObject(parsed.servers)) {
|
|
783
|
+
return {
|
|
784
|
+
status: "malformed",
|
|
785
|
+
entry: null,
|
|
786
|
+
error: "Invalid VS Code mcp.json schema: 'servers' must be a JSON object",
|
|
787
|
+
otherServersCount: 0,
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
const servers = parsed.servers;
|
|
791
|
+
const otherServersCount = Object.keys(servers).filter((k) => k !== serverName).length;
|
|
792
|
+
if (!(serverName in servers)) {
|
|
793
|
+
return {
|
|
794
|
+
status: "not_configured",
|
|
795
|
+
entry: null,
|
|
796
|
+
otherServersCount,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
const rawEntry = servers[serverName];
|
|
800
|
+
if (!isPlainObject(rawEntry)) {
|
|
801
|
+
return {
|
|
802
|
+
status: "invalid_entry",
|
|
803
|
+
entry: null,
|
|
804
|
+
rawEntry,
|
|
805
|
+
error: `Invalid VS Code MCP server entry '${serverName}': must be a JSON object`,
|
|
806
|
+
otherServersCount,
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
if (rawEntry.type !== undefined && rawEntry.type !== "stdio") {
|
|
810
|
+
return {
|
|
811
|
+
status: "invalid_entry",
|
|
812
|
+
entry: null,
|
|
813
|
+
rawEntry,
|
|
814
|
+
error: `Invalid VS Code MCP server entry '${serverName}': 'type' must be 'stdio'`,
|
|
815
|
+
otherServersCount,
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
if (typeof rawEntry.command !== "string" || rawEntry.command.trim().length === 0) {
|
|
819
|
+
return {
|
|
820
|
+
status: "invalid_entry",
|
|
821
|
+
entry: null,
|
|
822
|
+
rawEntry,
|
|
823
|
+
error: `Invalid VS Code MCP server entry '${serverName}': 'command' must be a non-empty string`,
|
|
824
|
+
otherServersCount,
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
let args = [];
|
|
828
|
+
if (rawEntry.args !== undefined) {
|
|
829
|
+
if (!Array.isArray(rawEntry.args) || !rawEntry.args.every((a) => typeof a === "string")) {
|
|
830
|
+
return {
|
|
831
|
+
status: "invalid_entry",
|
|
832
|
+
entry: null,
|
|
833
|
+
rawEntry,
|
|
834
|
+
error: `Invalid VS Code MCP server entry '${serverName}': 'args' must be an array of strings`,
|
|
835
|
+
otherServersCount,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
args = [...rawEntry.args];
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
status: "valid",
|
|
842
|
+
entry: {
|
|
843
|
+
type: "stdio",
|
|
844
|
+
command: rawEntry.command,
|
|
845
|
+
args,
|
|
846
|
+
},
|
|
847
|
+
rawEntry,
|
|
848
|
+
otherServersCount,
|
|
849
|
+
};
|
|
850
|
+
},
|
|
851
|
+
upsertServer(rawContent, serverName, reg) {
|
|
852
|
+
let root = {};
|
|
853
|
+
if (rawContent !== null && rawContent.trim().length > 0) {
|
|
854
|
+
let parsed;
|
|
855
|
+
try {
|
|
856
|
+
parsed = JSON.parse(rawContent);
|
|
857
|
+
}
|
|
858
|
+
catch (err) {
|
|
859
|
+
throw new Error(`Malformed VS Code mcp.json: ${err instanceof Error ? err.message : String(err)}`);
|
|
860
|
+
}
|
|
861
|
+
if (!isPlainObject(parsed)) {
|
|
862
|
+
throw new Error("Invalid VS Code mcp.json schema: root value must be a JSON object");
|
|
863
|
+
}
|
|
864
|
+
if (parsed.servers !== undefined && !isPlainObject(parsed.servers)) {
|
|
865
|
+
throw new Error("Invalid VS Code mcp.json schema: 'servers' must be a JSON object");
|
|
866
|
+
}
|
|
867
|
+
root = { ...parsed };
|
|
868
|
+
}
|
|
869
|
+
const existingServers = isPlainObject(root.servers) ? { ...root.servers } : {};
|
|
870
|
+
existingServers[serverName] = {
|
|
871
|
+
type: "stdio",
|
|
872
|
+
command: reg.command,
|
|
873
|
+
args: [...reg.args],
|
|
874
|
+
};
|
|
875
|
+
root.servers = existingServers;
|
|
876
|
+
return JSON.stringify(root, null, 2) + "\n";
|
|
877
|
+
},
|
|
878
|
+
removeServer(rawContent, serverName) {
|
|
879
|
+
let parsed;
|
|
880
|
+
try {
|
|
881
|
+
parsed = JSON.parse(rawContent);
|
|
882
|
+
}
|
|
883
|
+
catch (err) {
|
|
884
|
+
throw new Error(`Malformed VS Code mcp.json: ${err instanceof Error ? err.message : String(err)}`);
|
|
885
|
+
}
|
|
886
|
+
if (!isPlainObject(parsed)) {
|
|
887
|
+
throw new Error("Invalid VS Code mcp.json schema: root value must be a JSON object");
|
|
888
|
+
}
|
|
889
|
+
if (parsed.servers !== undefined && !isPlainObject(parsed.servers)) {
|
|
890
|
+
throw new Error("Invalid VS Code mcp.json schema: 'servers' must be a JSON object");
|
|
891
|
+
}
|
|
892
|
+
if (!isPlainObject(parsed.servers) || !(serverName in parsed.servers)) {
|
|
893
|
+
return { content: rawContent, removed: false };
|
|
894
|
+
}
|
|
895
|
+
const nextServers = { ...parsed.servers };
|
|
896
|
+
delete nextServers[serverName];
|
|
897
|
+
const nextRoot = { ...parsed, servers: nextServers };
|
|
898
|
+
return {
|
|
899
|
+
content: JSON.stringify(nextRoot, null, 2) + "\n",
|
|
900
|
+
removed: true,
|
|
901
|
+
};
|
|
902
|
+
},
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
function createJsonOpencodeAdapter() {
|
|
906
|
+
return {
|
|
907
|
+
format: "json-opencode",
|
|
908
|
+
readServer(rawContent, serverName = BLA_IDENTITY.mcpServerName) {
|
|
909
|
+
let parsed;
|
|
910
|
+
try {
|
|
911
|
+
parsed = JSON.parse(rawContent);
|
|
912
|
+
}
|
|
913
|
+
catch (err) {
|
|
914
|
+
return {
|
|
915
|
+
status: "malformed",
|
|
916
|
+
entry: null,
|
|
917
|
+
error: `Malformed OpenCode configuration: ${err instanceof Error ? err.message : String(err)}`,
|
|
918
|
+
otherServersCount: 0,
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
if (!isPlainObject(parsed)) {
|
|
922
|
+
return {
|
|
923
|
+
status: "malformed",
|
|
924
|
+
entry: null,
|
|
925
|
+
error: "Invalid OpenCode configuration schema: root value must be a JSON object",
|
|
926
|
+
otherServersCount: 0,
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
if (parsed.mcp === undefined) {
|
|
930
|
+
if ("mcpServers" in parsed) {
|
|
931
|
+
return {
|
|
932
|
+
status: "malformed",
|
|
933
|
+
entry: null,
|
|
934
|
+
error: "Invalid OpenCode configuration schema: expected root key 'mcp', found 'mcpServers'",
|
|
935
|
+
otherServersCount: 0,
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
return {
|
|
939
|
+
status: "not_configured",
|
|
940
|
+
entry: null,
|
|
941
|
+
otherServersCount: 0,
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
if (!isPlainObject(parsed.mcp)) {
|
|
945
|
+
return {
|
|
946
|
+
status: "malformed",
|
|
947
|
+
entry: null,
|
|
948
|
+
error: "Invalid OpenCode configuration schema: 'mcp' must be a JSON object",
|
|
949
|
+
otherServersCount: 0,
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
const mcp = parsed.mcp;
|
|
953
|
+
const otherServersCount = Object.keys(mcp).filter((k) => k !== serverName).length;
|
|
954
|
+
if (!(serverName in mcp)) {
|
|
955
|
+
return {
|
|
956
|
+
status: "not_configured",
|
|
957
|
+
entry: null,
|
|
958
|
+
otherServersCount,
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
const rawEntry = mcp[serverName];
|
|
962
|
+
if (!isPlainObject(rawEntry)) {
|
|
963
|
+
return {
|
|
964
|
+
status: "invalid_entry",
|
|
965
|
+
entry: null,
|
|
966
|
+
rawEntry,
|
|
967
|
+
error: `Invalid OpenCode MCP entry '${serverName}': must be a JSON object`,
|
|
968
|
+
otherServersCount,
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
if (rawEntry.type !== "local") {
|
|
972
|
+
return {
|
|
973
|
+
status: "invalid_entry",
|
|
974
|
+
entry: null,
|
|
975
|
+
rawEntry,
|
|
976
|
+
error: `Invalid OpenCode MCP entry '${serverName}': 'type' must be 'local'`,
|
|
977
|
+
otherServersCount,
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
if (!Array.isArray(rawEntry.command) ||
|
|
981
|
+
rawEntry.command.length === 0 ||
|
|
982
|
+
!rawEntry.command.every((c) => typeof c === "string" && c.length > 0) ||
|
|
983
|
+
rawEntry.command[0].trim().length === 0) {
|
|
984
|
+
return {
|
|
985
|
+
status: "invalid_entry",
|
|
986
|
+
entry: null,
|
|
987
|
+
rawEntry,
|
|
988
|
+
error: `Invalid OpenCode MCP entry '${serverName}': 'command' must be a non-empty string array`,
|
|
989
|
+
otherServersCount,
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
if (rawEntry.enabled !== undefined && typeof rawEntry.enabled !== "boolean") {
|
|
993
|
+
return {
|
|
994
|
+
status: "invalid_entry",
|
|
995
|
+
entry: null,
|
|
996
|
+
rawEntry,
|
|
997
|
+
error: `Invalid OpenCode MCP entry '${serverName}': 'enabled' must be a boolean`,
|
|
998
|
+
otherServersCount,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
if (rawEntry.enabled === false) {
|
|
1002
|
+
return {
|
|
1003
|
+
status: "invalid_entry",
|
|
1004
|
+
entry: null,
|
|
1005
|
+
rawEntry,
|
|
1006
|
+
error: `OpenCode MCP entry '${serverName}' is disabled (enabled: false)`,
|
|
1007
|
+
otherServersCount,
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
const cmdArray = rawEntry.command;
|
|
1011
|
+
return {
|
|
1012
|
+
status: "valid",
|
|
1013
|
+
entry: {
|
|
1014
|
+
type: "local",
|
|
1015
|
+
command: cmdArray[0],
|
|
1016
|
+
args: cmdArray.slice(1),
|
|
1017
|
+
enabled: rawEntry.enabled ?? true,
|
|
1018
|
+
},
|
|
1019
|
+
rawEntry,
|
|
1020
|
+
otherServersCount,
|
|
1021
|
+
};
|
|
1022
|
+
},
|
|
1023
|
+
upsertServer(rawContent, serverName, reg) {
|
|
1024
|
+
let root = {};
|
|
1025
|
+
if (rawContent !== null && rawContent.trim().length > 0) {
|
|
1026
|
+
let parsed;
|
|
1027
|
+
try {
|
|
1028
|
+
parsed = JSON.parse(rawContent);
|
|
1029
|
+
}
|
|
1030
|
+
catch (err) {
|
|
1031
|
+
throw new Error(`Malformed OpenCode configuration: ${err instanceof Error ? err.message : String(err)}`);
|
|
1032
|
+
}
|
|
1033
|
+
if (!isPlainObject(parsed)) {
|
|
1034
|
+
throw new Error("Invalid OpenCode configuration schema: root value must be a JSON object");
|
|
1035
|
+
}
|
|
1036
|
+
if (parsed.mcp !== undefined && !isPlainObject(parsed.mcp)) {
|
|
1037
|
+
throw new Error("Invalid OpenCode configuration schema: 'mcp' must be a JSON object");
|
|
1038
|
+
}
|
|
1039
|
+
root = { ...parsed };
|
|
1040
|
+
}
|
|
1041
|
+
const existingMcp = isPlainObject(root.mcp) ? { ...root.mcp } : {};
|
|
1042
|
+
existingMcp[serverName] = {
|
|
1043
|
+
type: "local",
|
|
1044
|
+
command: [reg.command, ...reg.args],
|
|
1045
|
+
enabled: reg.enabled ?? true,
|
|
1046
|
+
};
|
|
1047
|
+
root.mcp = existingMcp;
|
|
1048
|
+
return JSON.stringify(root, null, 2) + "\n";
|
|
1049
|
+
},
|
|
1050
|
+
removeServer(rawContent, serverName) {
|
|
1051
|
+
let parsed;
|
|
1052
|
+
try {
|
|
1053
|
+
parsed = JSON.parse(rawContent);
|
|
1054
|
+
}
|
|
1055
|
+
catch (err) {
|
|
1056
|
+
throw new Error(`Malformed OpenCode configuration: ${err instanceof Error ? err.message : String(err)}`);
|
|
1057
|
+
}
|
|
1058
|
+
if (!isPlainObject(parsed)) {
|
|
1059
|
+
throw new Error("Invalid OpenCode configuration schema: root value must be a JSON object");
|
|
1060
|
+
}
|
|
1061
|
+
if (parsed.mcp !== undefined && !isPlainObject(parsed.mcp)) {
|
|
1062
|
+
throw new Error("Invalid OpenCode configuration schema: 'mcp' must be a JSON object");
|
|
1063
|
+
}
|
|
1064
|
+
if (!isPlainObject(parsed.mcp) || !(serverName in parsed.mcp)) {
|
|
1065
|
+
return { content: rawContent, removed: false };
|
|
1066
|
+
}
|
|
1067
|
+
const nextMcp = { ...parsed.mcp };
|
|
1068
|
+
delete nextMcp[serverName];
|
|
1069
|
+
const nextRoot = { ...parsed, mcp: nextMcp };
|
|
1070
|
+
return {
|
|
1071
|
+
content: JSON.stringify(nextRoot, null, 2) + "\n",
|
|
1072
|
+
removed: true,
|
|
1073
|
+
};
|
|
1074
|
+
},
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
function createTomlCodexAdapter() {
|
|
1078
|
+
return {
|
|
1079
|
+
format: "toml-codex",
|
|
1080
|
+
readServer(rawContent, serverName = BLA_IDENTITY.mcpServerName) {
|
|
1081
|
+
return readCodexTomlMcp(rawContent, serverName);
|
|
1082
|
+
},
|
|
1083
|
+
upsertServer(rawContent, serverName, reg) {
|
|
1084
|
+
return writeCodexTomlMcp(rawContent, serverName, reg);
|
|
1085
|
+
},
|
|
1086
|
+
removeServer(rawContent, serverName) {
|
|
1087
|
+
return removeCodexTomlMcp(rawContent, serverName);
|
|
1088
|
+
},
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
const ADAPTERS = Object.freeze({
|
|
1092
|
+
"json-mcpServers": createJsonMcpServersAdapter(),
|
|
1093
|
+
"json-vscode": createJsonVscodeAdapter(),
|
|
1094
|
+
"json-opencode": createJsonOpencodeAdapter(),
|
|
1095
|
+
"toml-codex": createTomlCodexAdapter(),
|
|
1096
|
+
});
|
|
1097
|
+
const CLIENT_FORMAT_MAP = Object.freeze({
|
|
1098
|
+
claude_code: "json-mcpServers",
|
|
1099
|
+
claude_desktop: "json-mcpServers",
|
|
1100
|
+
cursor: "json-mcpServers",
|
|
1101
|
+
codex: "toml-codex",
|
|
1102
|
+
gemini_cli: "json-mcpServers",
|
|
1103
|
+
antigravity: "json-mcpServers",
|
|
1104
|
+
windsurf: "json-mcpServers",
|
|
1105
|
+
vscode: "json-vscode",
|
|
1106
|
+
opencode: "json-opencode",
|
|
1107
|
+
copilot_cli: "json-mcpServers",
|
|
1108
|
+
});
|
|
1109
|
+
export function getClientConfigAdapter(clientOrFormat) {
|
|
1110
|
+
if (typeof clientOrFormat === "object" && clientOrFormat !== null && "format" in clientOrFormat) {
|
|
1111
|
+
return ADAPTERS[clientOrFormat.format];
|
|
1112
|
+
}
|
|
1113
|
+
if (typeof clientOrFormat === "string") {
|
|
1114
|
+
if (clientOrFormat in ADAPTERS) {
|
|
1115
|
+
return ADAPTERS[clientOrFormat];
|
|
1116
|
+
}
|
|
1117
|
+
const normalized = normalizeClientId(clientOrFormat);
|
|
1118
|
+
if (normalized) {
|
|
1119
|
+
return ADAPTERS[CLIENT_FORMAT_MAP[normalized]];
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
return ADAPTERS["json-mcpServers"];
|
|
1123
|
+
}
|
|
1124
|
+
export function readClientMcpConfig(configPath, format, serverName = BLA_IDENTITY.mcpServerName) {
|
|
1125
|
+
if (!existsSync(configPath)) {
|
|
1126
|
+
return {
|
|
1127
|
+
status: "missing",
|
|
1128
|
+
entry: null,
|
|
1129
|
+
otherServersCount: 0,
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
let rawContent;
|
|
1133
|
+
try {
|
|
1134
|
+
rawContent = readFileSync(configPath, "utf8");
|
|
1135
|
+
}
|
|
1136
|
+
catch (err) {
|
|
1137
|
+
return {
|
|
1138
|
+
status: "malformed",
|
|
1139
|
+
entry: null,
|
|
1140
|
+
error: `Failed to read configuration file '${configPath}': ${err instanceof Error ? err.message : String(err)}`,
|
|
1141
|
+
otherServersCount: 0,
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
return getClientConfigAdapter(format).readServer(rawContent, serverName);
|
|
1145
|
+
}
|
|
1146
|
+
export function resolveSupportedAgentClients(home = homedir(), platform = process.platform, env) {
|
|
1147
|
+
const winAppData = env?.APPDATA && env.APPDATA.trim().length > 0
|
|
1148
|
+
? env.APPDATA
|
|
1149
|
+
: join(home, "AppData", "Roaming");
|
|
1150
|
+
const xdgConfigHome = env?.XDG_CONFIG_HOME && env.XDG_CONFIG_HOME.trim().length > 0
|
|
1151
|
+
? env.XDG_CONFIG_HOME
|
|
1152
|
+
: join(home, ".config");
|
|
5
1153
|
const claudeDesktopDir = platform === "darwin"
|
|
6
1154
|
? join(home, "Library", "Application Support", "Claude")
|
|
7
1155
|
: platform === "win32"
|
|
8
|
-
? join(
|
|
9
|
-
: join(
|
|
1156
|
+
? join(winAppData, "Claude")
|
|
1157
|
+
: join(xdgConfigHome, "Claude");
|
|
10
1158
|
const vscodeDir = platform === "darwin"
|
|
11
1159
|
? join(home, "Library", "Application Support", "Code", "User")
|
|
12
1160
|
: platform === "win32"
|
|
13
|
-
? join(
|
|
14
|
-
: join(
|
|
1161
|
+
? join(winAppData, "Code", "User")
|
|
1162
|
+
: join(xdgConfigHome, "Code", "User");
|
|
1163
|
+
const opencodeDir = join(xdgConfigHome, "opencode");
|
|
15
1164
|
const rawDefs = [
|
|
16
1165
|
{
|
|
17
1166
|
id: "claude_code",
|
|
18
1167
|
displayName: "Claude Code",
|
|
19
1168
|
configPath: join(home, ".claude.json"),
|
|
20
|
-
|
|
1169
|
+
format: "json-mcpServers",
|
|
1170
|
+
// Never probe `dirname(configPath)` because for `~/.claude.json` that is `home` itself!
|
|
1171
|
+
probePaths: [join(home, ".claude.json"), join(home, ".claude")],
|
|
21
1172
|
skillDirectory: join(home, ".claude", "skills", "blind-llm-agents"),
|
|
22
1173
|
},
|
|
23
1174
|
{
|
|
24
1175
|
id: "claude_desktop",
|
|
25
1176
|
displayName: "Claude Desktop",
|
|
26
1177
|
configPath: join(claudeDesktopDir, "claude_desktop_config.json"),
|
|
27
|
-
|
|
1178
|
+
format: "json-mcpServers",
|
|
1179
|
+
probePaths: [join(claudeDesktopDir, "claude_desktop_config.json"), claudeDesktopDir],
|
|
28
1180
|
skillDirectory: join(home, ".agents", "skills", "blind-llm-agents"),
|
|
29
1181
|
},
|
|
30
1182
|
{
|
|
31
1183
|
id: "cursor",
|
|
32
1184
|
displayName: "Cursor",
|
|
33
1185
|
configPath: join(home, ".cursor", "mcp.json"),
|
|
34
|
-
|
|
1186
|
+
format: "json-mcpServers",
|
|
1187
|
+
probePaths: [join(home, ".cursor", "mcp.json"), join(home, ".cursor")],
|
|
35
1188
|
skillDirectory: join(home, ".cursor", "skills", "blind-llm-agents"),
|
|
36
1189
|
},
|
|
37
1190
|
{
|
|
38
1191
|
id: "codex",
|
|
39
1192
|
displayName: "Codex CLI",
|
|
40
|
-
configPath: join(home, ".codex", "
|
|
41
|
-
|
|
1193
|
+
configPath: join(home, ".codex", "config.toml"),
|
|
1194
|
+
format: "toml-codex",
|
|
1195
|
+
probePaths: [join(home, ".codex", "config.toml"), join(home, ".codex")],
|
|
42
1196
|
skillDirectory: join(home, ".codex", "skills", "blind-llm-agents"),
|
|
43
1197
|
},
|
|
44
1198
|
{
|
|
45
1199
|
id: "gemini_cli",
|
|
46
1200
|
displayName: "Gemini CLI",
|
|
47
1201
|
configPath: join(home, ".gemini", "settings.json"),
|
|
48
|
-
|
|
1202
|
+
format: "json-mcpServers",
|
|
1203
|
+
probePaths: [join(home, ".gemini", "settings.json"), join(home, ".gemini")],
|
|
49
1204
|
skillDirectory: join(home, ".gemini", "skills", "blind-llm-agents"),
|
|
50
1205
|
},
|
|
51
1206
|
{
|
|
52
1207
|
id: "antigravity",
|
|
53
1208
|
displayName: "Google Antigravity",
|
|
54
1209
|
configPath: join(home, ".gemini", "antigravity", "mcp_config.json"),
|
|
55
|
-
|
|
1210
|
+
format: "json-mcpServers",
|
|
1211
|
+
probePaths: [
|
|
1212
|
+
join(home, ".gemini", "antigravity", "mcp_config.json"),
|
|
1213
|
+
join(home, ".gemini", "antigravity"),
|
|
1214
|
+
],
|
|
56
1215
|
skillDirectory: join(home, ".gemini", "antigravity", "skills", "blind-llm-agents"),
|
|
57
1216
|
},
|
|
58
1217
|
{
|
|
59
1218
|
id: "windsurf",
|
|
60
1219
|
displayName: "Windsurf",
|
|
61
1220
|
configPath: join(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
62
|
-
|
|
1221
|
+
format: "json-mcpServers",
|
|
1222
|
+
probePaths: [
|
|
1223
|
+
join(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
1224
|
+
join(home, ".codeium", "windsurf"),
|
|
1225
|
+
],
|
|
63
1226
|
skillDirectory: join(home, ".agents", "skills", "blind-llm-agents"),
|
|
64
1227
|
},
|
|
65
1228
|
{
|
|
66
1229
|
id: "vscode",
|
|
67
1230
|
displayName: "VS Code",
|
|
68
1231
|
configPath: join(vscodeDir, "mcp.json"),
|
|
69
|
-
|
|
1232
|
+
format: "json-vscode",
|
|
1233
|
+
probePaths: [join(vscodeDir, "mcp.json"), vscodeDir],
|
|
70
1234
|
skillDirectory: join(home, ".agents", "skills", "blind-llm-agents"),
|
|
71
1235
|
},
|
|
72
1236
|
{
|
|
73
1237
|
id: "opencode",
|
|
74
1238
|
displayName: "OpenCode",
|
|
75
|
-
configPath: join(
|
|
76
|
-
|
|
1239
|
+
configPath: join(opencodeDir, "opencode.json"),
|
|
1240
|
+
format: "json-opencode",
|
|
1241
|
+
probePaths: [join(opencodeDir, "opencode.json"), opencodeDir],
|
|
77
1242
|
skillDirectory: join(home, ".agents", "skills", "blind-llm-agents"),
|
|
78
1243
|
},
|
|
79
1244
|
{
|
|
80
1245
|
id: "copilot_cli",
|
|
81
1246
|
displayName: "GitHub Copilot CLI",
|
|
82
1247
|
configPath: join(home, ".copilot", "mcp-config.json"),
|
|
83
|
-
|
|
1248
|
+
format: "json-mcpServers",
|
|
1249
|
+
probePaths: [join(home, ".copilot", "mcp-config.json"), join(home, ".copilot")],
|
|
84
1250
|
skillDirectory: join(home, ".agents", "skills", "blind-llm-agents"),
|
|
85
1251
|
},
|
|
86
1252
|
];
|
|
@@ -89,7 +1255,14 @@ export function resolveSupportedAgentClients(home = homedir(), platform = proces
|
|
|
89
1255
|
displayName: def.displayName,
|
|
90
1256
|
configPath: def.configPath,
|
|
91
1257
|
skillDirectory: def.skillDirectory,
|
|
92
|
-
|
|
1258
|
+
format: def.format,
|
|
1259
|
+
probePaths: def.probePaths,
|
|
1260
|
+
detected: def.probePaths.some((p) => existsSync(p)),
|
|
93
1261
|
}));
|
|
94
1262
|
}
|
|
1263
|
+
export function detectInstalledClients(home = homedir(), platform = process.platform, env) {
|
|
1264
|
+
return resolveSupportedAgentClients(home, platform, env)
|
|
1265
|
+
.filter((c) => c.detected)
|
|
1266
|
+
.map((c) => c.id);
|
|
1267
|
+
}
|
|
95
1268
|
//# sourceMappingURL=clients.js.map
|