Skip to content

Commit 627bb33

Browse files
committed
feat(installer): configure mind-map MCP in OpenCode + fix silent-failure bug
OpenCode (https://opencode.ai) is now detected and configured alongside Claude / Copilot / VS Code / Cursor. Its MCP shape differs: - top-level key 'mcp' (not 'mcpServers') - command is a JSON array (not string + separate args) - 'enabled': true is expected - schema URL pointer A new configure_opencode() function emits and merges this shape. Detection follows the same pattern used for other clients: the config file already exists, the config directory has been created, or the 'opencode' binary is on PATH. On Linux/macOS we write ~/.config/opencode/opencode.json; on Windows %APPDATA%\opencode\opencode.json. SKILL.md gets dropped into the new client's primary skill discovery path too (~/.config/opencode/skills/mind-map/SKILL.md and %APPDATA%\opencode\skills\mind-map\), although OpenCode already finds SKILL.md via its ~/.claude/skills/ and ~/.agents/skills/ fallback paths which the installer was already writing. Also fixes a pre-existing silent-failure bug in configure_mcp_client: the embedded python3 block interpolated the JSON entry string directly into Python source via shell quoting. For any existing config file (i.e. any real-world install where Claude/Copilot/Cursor/VS Code is already set up), Python raised SyntaxError on the embedded double-quotes, the error was redirected to /dev/null, and the script reported '! could not update' with no diagnostics. The fix passes the JSON entry via an environment variable instead, which sidesteps the quoting altogether. Verified end-to-end in the devcontainer against both fresh-file and merge-into-existing scenarios for both the OpenCode and Claude shapes.
1 parent 2e677a5 commit 627bb33

2 files changed

Lines changed: 130 additions & 11 deletions

File tree

install.ps1

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ $SkillDirs = @(
125125
"$env:USERPROFILE\.copilot\skills\mind-map"
126126
"$env:USERPROFILE\.claude\skills\mind-map"
127127
"$env:USERPROFILE\.agents\skills\mind-map"
128+
"$env:APPDATA\opencode\skills\mind-map"
128129
)
129130

130131
foreach ($dir in $SkillDirs) {
@@ -288,6 +289,62 @@ if (Test-Path "$env:USERPROFILE\.cursor") {
288289
Set-McpConfig "$env:USERPROFILE\.cursor\mcp.json" "Cursor"
289290
}
290291

292+
# OpenCode (https://opencode.ai) — different config shape: top-level "mcp"
293+
# (not "mcpServers"), command is an array, and entries carry "enabled": true.
294+
# Primary path on Windows is %APPDATA%\opencode\opencode.json; the script also
295+
# accepts the .jsonc variant if it's the file the user already has.
296+
$openCodeEntry = [PSCustomObject]@{
297+
type = "local"
298+
command = @($BinaryPath)
299+
enabled = $true
300+
}
301+
302+
function Set-OpenCodeMcpConfig {
303+
param([string]$ConfigPath)
304+
try {
305+
if (Test-Path $ConfigPath) {
306+
$content = Get-Content -Raw $ConfigPath | ConvertFrom-Json
307+
if (-not $content.mcp) {
308+
$content | Add-Member -NotePropertyName "mcp" -NotePropertyValue ([PSCustomObject]@{})
309+
}
310+
if ($content.mcp.PSObject.Properties.Name -contains "mind-map") {
311+
$content.mcp.PSObject.Properties.Remove("mind-map")
312+
}
313+
$content.mcp | Add-Member -NotePropertyName "mind-map" -NotePropertyValue $openCodeEntry
314+
$content | ConvertTo-Json -Depth 10 | Set-Content $ConfigPath -Encoding UTF8
315+
Write-Ok "OpenCode — configured in $ConfigPath"
316+
} else {
317+
$dir = Split-Path $ConfigPath -Parent
318+
if ($dir) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
319+
$config = [PSCustomObject]@{
320+
'$schema' = "https://opencode.ai/config.json"
321+
mcp = [PSCustomObject]@{
322+
"mind-map" = $openCodeEntry
323+
}
324+
}
325+
$config | ConvertTo-Json -Depth 10 | Set-Content $ConfigPath -Encoding UTF8
326+
Write-Ok "OpenCode — created $ConfigPath"
327+
}
328+
} catch {
329+
Write-Warn "OpenCode — could not update $ConfigPath"
330+
}
331+
}
332+
333+
$openCodeDir = "$env:APPDATA\opencode"
334+
$openCodeJson = Join-Path $openCodeDir "opencode.json"
335+
$openCodeJsonc = Join-Path $openCodeDir "opencode.jsonc"
336+
$openCodeCfg = $null
337+
if (Test-Path $openCodeJson) {
338+
$openCodeCfg = $openCodeJson
339+
} elseif (Test-Path $openCodeJsonc) {
340+
$openCodeCfg = $openCodeJsonc
341+
} elseif ((Test-Path $openCodeDir) -or (Get-Command opencode -ErrorAction SilentlyContinue)) {
342+
$openCodeCfg = $openCodeJson
343+
}
344+
if ($openCodeCfg) {
345+
Set-OpenCodeMcpConfig $openCodeCfg
346+
}
347+
291348
# ---------------------------------------------------------------------------
292349
# Done
293350
# ---------------------------------------------------------------------------
@@ -305,4 +362,4 @@ Write-Host "To uninstall mind-map completely:" -ForegroundColor DarkGray
305362
Write-Host " mind-map service uninstall # remove service (if installed)" -ForegroundColor DarkGray
306363
Write-Host " Remove-Item -Recurse '$InstallDir' # remove binary" -ForegroundColor DarkGray
307364
Write-Host " Remove-Item -Recurse '$env:USERPROFILE\.mind-map' # remove wiki data" -ForegroundColor DarkGray
308-
Write-Host " Remove-Item -Recurse '$env:USERPROFILE\.copilot\skills\mind-map', '$env:USERPROFILE\.claude\skills\mind-map', '$env:USERPROFILE\.agents\skills\mind-map'" -ForegroundColor DarkGray
365+
Write-Host " Remove-Item -Recurse '$env:USERPROFILE\.copilot\skills\mind-map', '$env:USERPROFILE\.claude\skills\mind-map', '$env:USERPROFILE\.agents\skills\mind-map', '$env:APPDATA\opencode\skills\mind-map'" -ForegroundColor DarkGray

install.sh

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ SKILL_DIRS=(
127127
"${HOME}/.copilot/skills/mind-map"
128128
"${HOME}/.claude/skills/mind-map"
129129
"${HOME}/.agents/skills/mind-map"
130+
"${HOME}/.config/opencode/skills/mind-map"
130131
)
131132

132133
echo ""
@@ -265,23 +266,68 @@ configure_mcp_client() {
265266
MCPEOF
266267
echo " + ${client_name} -- created ${config_file}"
267268
elif command -v python3 >/dev/null 2>&1; then
268-
python3 -c "
269-
import json
270-
path = '${config_file}'
269+
# Pass values via env vars to avoid shell-quoting the JSON literal into
270+
# Python source code (which breaks on every embedded double-quote).
271+
MM_CFG="$config_file" MM_ENTRY="$mcp_entry" MM_CLIENT="$client_name" \
272+
python3 -c '
273+
import json, os
274+
path = os.environ["MM_CFG"]
275+
client = os.environ["MM_CLIENT"]
271276
with open(path) as f:
272277
data = json.load(f)
273-
servers = data.setdefault('mcpServers', {})
274-
entry = json.loads('${mcp_entry}')
275-
servers['mind-map'] = entry
276-
with open(path, 'w') as f:
278+
servers = data.setdefault("mcpServers", {})
279+
servers["mind-map"] = json.loads(os.environ["MM_ENTRY"])
280+
with open(path, "w") as f:
277281
json.dump(data, f, indent=2)
278-
print(' + ${client_name} -- updated ${config_file}')
279-
" 2>/dev/null || echo " ! ${client_name} -- could not update ${config_file}"
282+
print(f" + {client} -- updated {path}")
283+
' || echo " ! ${client_name} -- could not update ${config_file}"
280284
else
281285
echo " ! ${client_name} -- exists but python3 not available to merge"
282286
fi
283287
}
284288

289+
# OpenCode uses a different MCP config shape than Claude/Copilot/VS Code/Cursor:
290+
# - Top-level key is "mcp" (not "mcpServers")
291+
# - command is an array (not a string + separate args)
292+
# - "enabled": true is expected on the entry
293+
# - JSONC (JSON with comments) is supported but we always emit plain JSON
294+
# See https://opencode.ai/docs/mcp-servers
295+
configure_opencode() {
296+
local config_file="$1"
297+
298+
# OpenCode entry, written with command as a JSON array.
299+
local mcp_entry
300+
mcp_entry="{\"type\": \"local\", \"command\": [\"${INSTALL_DIR}/mind-map\"], \"enabled\": true}"
301+
302+
if [ ! -f "$config_file" ]; then
303+
mkdir -p "$(dirname "$config_file")"
304+
cat > "$config_file" << OPENCODEEOF
305+
{
306+
"\$schema": "https://opencode.ai/config.json",
307+
"mcp": {
308+
"mind-map": ${mcp_entry}
309+
}
310+
}
311+
OPENCODEEOF
312+
echo " + OpenCode -- created ${config_file}"
313+
elif command -v python3 >/dev/null 2>&1; then
314+
MM_CFG="$config_file" MM_ENTRY="$mcp_entry" \
315+
python3 -c '
316+
import json, os
317+
path = os.environ["MM_CFG"]
318+
with open(path) as f:
319+
data = json.load(f)
320+
servers = data.setdefault("mcp", {})
321+
servers["mind-map"] = json.loads(os.environ["MM_ENTRY"])
322+
with open(path, "w") as f:
323+
json.dump(data, f, indent=2)
324+
print(f" + OpenCode -- updated {path}")
325+
' || echo " ! OpenCode -- could not update ${config_file}"
326+
else
327+
echo " ! OpenCode -- exists but python3 not available to merge"
328+
fi
329+
}
330+
285331
echo ""
286332
echo "==> Configuring MCP clients..."
287333

@@ -308,6 +354,22 @@ fi
308354
# Claude Code
309355
configure_mcp_client "${HOME}/.claude.json" "Claude Code"
310356

357+
# OpenCode (https://opencode.ai). The studio reads opencode.json first, then
358+
# opencode.jsonc. If neither exists but the binary is on PATH or the config
359+
# directory has been created, we write the canonical .json so future runs
360+
# pick it up.
361+
OPENCODE_CFG=""
362+
if [ -f "${HOME}/.config/opencode/opencode.json" ]; then
363+
OPENCODE_CFG="${HOME}/.config/opencode/opencode.json"
364+
elif [ -f "${HOME}/.config/opencode/opencode.jsonc" ]; then
365+
OPENCODE_CFG="${HOME}/.config/opencode/opencode.jsonc"
366+
elif [ -d "${HOME}/.config/opencode" ] || command -v opencode >/dev/null 2>&1; then
367+
OPENCODE_CFG="${HOME}/.config/opencode/opencode.json"
368+
fi
369+
if [ -n "$OPENCODE_CFG" ]; then
370+
configure_opencode "$OPENCODE_CFG"
371+
fi
372+
311373
echo ""
312374
if [ "$INSTALL_SERVICE" = "y" ] || [ "$INSTALL_SERVICE" = "Y" ]; then
313375
echo "Done! mind-map is running as a service."
@@ -321,5 +383,5 @@ echo "To uninstall mind-map completely:"
321383
echo " sudo mind-map service uninstall # remove service (if installed)"
322384
echo " rm ${INSTALL_DIR}/mind-map # remove binary"
323385
echo " rm -rf ~/.mind-map # remove wiki data"
324-
echo " rm -rf ~/.copilot/skills/mind-map ~/.claude/skills/mind-map ~/.agents/skills/mind-map"
386+
echo " rm -rf ~/.copilot/skills/mind-map ~/.claude/skills/mind-map ~/.agents/skills/mind-map ~/.config/opencode/skills/mind-map"
325387
echo ""

0 commit comments

Comments
 (0)