From 5ca1bffeff4528cd05dd8e75b0720381f6261cbd Mon Sep 17 00:00:00 2001 From: Mofei Zhu Date: Thu, 22 Oct 2026 14:41:59 +0300 Subject: [PATCH 1/6] Ask the installer's MCP question without a slow pre-check Before asking, install.sh and install.ps1 ran `mapbox mcp install --global --dry-run`, which runs each client's own CLI to see what is already registered. Claude Code's takes over a second per call, so the screen sat still for 3-15 seconds between the two questions, even for anyone who then answered no or had no terminal. Whether to ask is now a PATH lookup for the client CLIs src/mcp.rs drives, honoring the same override variables. `mapbox mcp install` still does the full check after a yes. A test in src/mcp.rs keeps both installers' client lists in step with CLIENTS. --- CHANGELOG.md | 6 ++ scripts/install.ps1 | 149 +++++++++++++++++++++------------------ scripts/install.sh | 133 +++++++++++++++++++--------------- scripts/test-install.ps1 | 37 +++++++++- scripts/test-install.sh | 78 ++++++++++++++++---- src/mcp.rs | 34 +++++++++ 6 files changed, 295 insertions(+), 142 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0333269..2a98167 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,12 @@ that may never merge. They are not releases and are not listed here. ## Unreleased +- `install.sh`/`install.ps1`: the MCP question no longer waits several + seconds before it appears. The installer now asks whenever a supported + coding agent's CLI is on `PATH`, and only checks what is already + registered after a yes. A reinstall therefore asks again even when every + server is registered; a yes then reports each one as already there. + ## 1.1.0 - 2026-10-22 - `mapbox workflow` (beta): install and run workflows, which are named, diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 3974070..f554082 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -838,84 +838,95 @@ Windows 11 on Arm has and Windows 10 on Arm does not. # # A separate question from the skill above: this registers a hosted # server in the coding agent's own config, next to servers the user - # added, rather than writing a directory this CLI owns. `mapbox mcp - # install` never replaces an entry that is already there (src/mcp.rs), - # so on a reinstall it is only offered when something is left to - # register. Same opt-out and same no-console rule as the skill. + # added, rather than writing a directory this CLI owns. Same opt-out + # and same no-console rule as the skill. + # + # Whether to ask is a PATH lookup and nothing more, for the reason + # install.sh gives: checking what is already registered runs each + # client's own CLI, which took seconds with nothing on screen. + # `mapbox mcp install` does that check itself once someone says yes, + # so a reinstall asks again and a yes reports what was already there. if ($AgentSetupAllowed) { $previousErrorAction = $ErrorActionPreference $ErrorActionPreference = 'Continue' try { - # Fails with mcp_client_not_found when no supported client is - # installed, which is most machines: nothing to offer, so - # nothing is said. - $mcpCheck = (& $destination mcp install --global --dry-run -o json 2>$null) -join '' - if ($LASTEXITCODE -eq 0) { - $clientLabels = @{ - 'claude-code' = 'Claude Code'; 'codex' = 'Codex' - 'vscode' = 'VS Code'; 'cursor' = 'Cursor' + $clientLabels = @{ + 'claude-code' = 'Claude Code'; 'codex' = 'Codex' + 'vscode' = 'VS Code'; 'cursor' = 'Cursor' + } + # One row per client in src/mcp.rs's CLIENTS, honoring the + # same override variable it does; + # `installers_know_every_mcp_client` there holds this list to + # that table. Most machines have none of these. + $mcpFound = @() + foreach ($client in @( + @{ Id = 'claude-code'; Var = 'MAPBOX_CLAUDE_CLI'; Bin = 'claude' }, + @{ Id = 'codex'; Var = 'MAPBOX_CODEX_CLI'; Bin = 'codex' }, + @{ Id = 'vscode'; Var = 'MAPBOX_CODE_CLI'; Bin = 'code' }, + @{ Id = 'cursor'; Var = 'MAPBOX_CURSOR_CLI'; Bin = 'cursor' })) { + $bin = [Environment]::GetEnvironmentVariable($client.Var) + if (-not $bin) { $bin = $client.Bin } + if (Get-Command $bin -CommandType Application -ErrorAction SilentlyContinue) { + $mcpFound += $clientLabels[$client.Id] + } + } + $mcpClients = $mcpFound -join ', ' + if ($mcpClients) { + $doMcpSetup = $false + if (-not [Console]::IsInputRedirected) { + Write-AskTitle "Add the Mapbox MCP servers to ${mcpClients}?" + Write-AskNote @('This runs ', 'mapbox mcp install --global', ', now or any time later.') + $answer = Read-AskAnswer 'Add them now?' + $Layout.Indent = ' ' + if ($answer -match '^(?i:y|yes)$') { $doMcpSetup = $true } } - $mcpClients = @(($mcpCheck | ConvertFrom-Json).results | - Where-Object { $_.status -eq 'would_install' } | - ForEach-Object { $_.client } | Select-Object -Unique | - ForEach-Object { if ($clientLabels.ContainsKey($_)) { $clientLabels[$_] } else { $_ } }) -join ', ' - if ($mcpClients) { - $doMcpSetup = $false - if (-not [Console]::IsInputRedirected) { - Write-AskTitle "Add the Mapbox MCP servers to ${mcpClients}?" - Write-AskNote @('This runs ', 'mapbox mcp install --global', ', now or any time later.') - $answer = Read-AskAnswer 'Add them now?' - $Layout.Indent = ' ' - if ($answer -match '^(?i:y|yes)$') { $doMcpSetup = $true } + if ($doMcpSetup) { + # Each client's own CLI does the registering, and + # Codex's can stop to print a sign-in URL, so + # stderr - where mapbox forwards what they print - + # is left on the console, live. No spinner: a + # second writer to the console is not worth it + # here without a terminal to test it on. + Write-Part $Layout.Indent + Write-Part '...' 'Cyan' + Write-Host ' Adding the Mapbox MCP servers' + $mcpOut = (& $destination mcp install --global -o json) -join '' + $mcpOk = $LASTEXITCODE -eq 0 + # One line per server and outcome, clients joined, + # so a server added just now reads differently from + # one that was already there. + $serverLabels = @{ 'mapbox' = 'Mapbox MCP'; 'mapbox-devkit' = 'Mapbox DevKit MCP' } + $rows = @() + try { $rows = @(($mcpOut | ConvertFrom-Json).results) } catch { $rows = @() } + $groups = [ordered]@{} + foreach ($row in $rows) { + $what = if ($serverLabels.ContainsKey($row.server)) { $serverLabels[$row.server] } else { $row.server } + $who = if ($clientLabels.ContainsKey($row.client)) { $clientLabels[$row.client] } else { $row.client } + $key = "$($row.status)|$what" + if (-not $groups.Contains($key)) { $groups[$key] = @() } + $groups[$key] += $who } - if ($doMcpSetup) { - # Each client's own CLI does the registering, and - # Codex's can stop to print a sign-in URL, so - # stderr - where mapbox forwards what they print - - # is left on the console, live. No spinner: a - # second writer to the console is not worth it - # here without a terminal to test it on. - Write-Part $Layout.Indent - Write-Part '...' 'Cyan' - Write-Host ' Adding the Mapbox MCP servers' - $mcpOut = (& $destination mcp install --global -o json) -join '' - $mcpOk = $LASTEXITCODE -eq 0 - # One line per server and outcome, clients joined, - # so a server added just now reads differently from - # one that was already there. - $serverLabels = @{ 'mapbox' = 'Mapbox MCP'; 'mapbox-devkit' = 'Mapbox DevKit MCP' } - $rows = @() - try { $rows = @(($mcpOut | ConvertFrom-Json).results) } catch { $rows = @() } - $groups = [ordered]@{} - foreach ($row in $rows) { - $what = if ($serverLabels.ContainsKey($row.server)) { $serverLabels[$row.server] } else { $row.server } - $who = if ($clientLabels.ContainsKey($row.client)) { $clientLabels[$row.client] } else { $row.client } - $key = "$($row.status)|$what" - if (-not $groups.Contains($key)) { $groups[$key] = @() } - $groups[$key] += $who + foreach ($key in $groups.Keys) { + $status, $what = $key -split '\|', 2 + $who = $groups[$key] -join ', ' + switch ($status) { + 'installed' { Write-Ok "Added $what to $who" } + 'already_installed' { Write-Skip "$what was already in $who" } + 'installed_login_incomplete' { Write-Warn "Added $what to $who, but sign-in did not finish" } + 'client_not_found' { Write-Warn "Skipped $what for ${who}: CLI not on PATH" } + 'config_unreadable' { Write-Warn "Skipped $what for ${who}: config could not be read" } + default { Write-Warn "Could not add $what to $who" } } - foreach ($key in $groups.Keys) { - $status, $what = $key -split '\|', 2 - $who = $groups[$key] -join ', ' - switch ($status) { - 'installed' { Write-Ok "Added $what to $who" } - 'already_installed' { Write-Skip "$what was already in $who" } - 'installed_login_incomplete' { Write-Warn "Added $what to $who, but sign-in did not finish" } - 'client_not_found' { Write-Warn "Skipped $what for ${who}: CLI not on PATH" } - 'config_unreadable' { Write-Warn "Skipped $what for ${who}: config could not be read" } - default { Write-Warn "Could not add $what to $who" } - } - } - if (-not $mcpOk) { - if ($rows.Count -eq 0) { Write-Warn 'Could not add the Mapbox MCP servers.' } - Write-Detail "Run 'mapbox mcp install --global' to see why and try again." - } - } else { - Write-Skip "Mapbox MCP servers not added to $mcpClients. Run this any time:" - Write-Detail 'mapbox mcp install --global' } - $Layout.Indent = ' ' + if (-not $mcpOk) { + if ($rows.Count -eq 0) { Write-Warn 'Could not add the Mapbox MCP servers.' } + Write-Detail "Run 'mapbox mcp install --global' to see why and try again." + } + } else { + Write-Skip "Mapbox MCP servers not added to $mcpClients. Run this any time:" + Write-Detail 'mapbox mcp install --global' } + $Layout.Indent = ' ' } } finally { $ErrorActionPreference = $previousErrorAction diff --git a/scripts/install.sh b/scripts/install.sh index a458d9b..195da4a 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1010,10 +1010,17 @@ fi # # A separate question from the skill above: this registers a hosted server in # the coding agent's own config, next to servers the user added, rather than -# writing a directory this CLI owns. `mapbox mcp install` never replaces an -# entry that is already there (src/mcp.rs), so on a reinstall it is only -# offered when something is left to register. Same opt-out and same -# no-terminal rule as the skill. +# writing a directory this CLI owns. Same opt-out and same no-terminal rule as +# the skill. +# +# Whether to ask is a PATH lookup and nothing more. Checking what is already +# registered means running each client's own CLI, and Claude Code's takes over +# a second per call, so the screen sat still for several seconds between the +# two questions, and for anyone who answers no or has no terminal that wait +# bought nothing. `mapbox mcp install` does that check itself once someone +# says yes, and never replaces an entry that is already there (src/mcp.rs). A +# reinstall therefore asks again even when everything is registered, and a +# yes then reports each server as already there. # "status|server|client" per row of `mapbox mcp install -o json`, with the # labels src/mcp.rs prints in place of its ids. @@ -1046,65 +1053,77 @@ mcp_grouped() { # mcp_grouped } END { for (i = 1; i <= n; i++) print order[i] "|" who[order[i]] }' } -# The clients a dry run would register anything with, comma-joined. -mcp_clients_from() { # mcp_clients_from - mcp_rows "$1" | awk -F'|' '$1 == "would_install" && !seen[$3]++ { print $3 }' | - paste -sd, - | sed 's/,/, /g' +# The MCP clients whose CLI is on PATH, comma-joined. One row per client in +# src/mcp.rs's CLIENTS, as "override variable|binary|label", honoring the same +# override variable it does; `installers_know_every_mcp_client` there holds +# this list to that table. +mcp_clients_present() { + for mcp_client in \ + 'MAPBOX_CLAUDE_CLI|claude|Claude Code' \ + 'MAPBOX_CODEX_CLI|codex|Codex' \ + 'MAPBOX_CODE_CLI|code|VS Code' \ + 'MAPBOX_CURSOR_CLI|cursor|Cursor'; do + mcp_var="${mcp_client%%|*}" + mcp_rest="${mcp_client#*|}" + mcp_label="${mcp_rest#*|}" + mcp_bin="$(printenv "$mcp_var" || true)" + [ -n "$mcp_bin" ] || mcp_bin="${mcp_rest%%|*}" + if command -v "$mcp_bin" >/dev/null 2>&1; then + printf '%s\n' "$mcp_label" + fi + done | paste -sd, - | sed 's/,/, /g' } if agent_setup_allowed; then - # Fails with mcp_client_not_found when no supported client is installed, - # which is most machines: nothing to offer, so nothing is said. - if mcp_check=$(log_capture "${INSTALL_DIR}/mapbox" mcp install --global --dry-run -o json); then - mcp_clients="$(mcp_clients_from "$mcp_check")" - if [ -n "$mcp_clients" ]; then - do_mcp_setup=no - if have_tty; then - ask_title "Add the Mapbox MCP servers to ${mcp_clients}?" - ask_note "This runs $(ask_cmd 'mapbox mcp install --global'), now or any time later." - answer="$(ask " Add them now? ${DIM}[y/N]${RESET} ")" || answer='' - is_yes "$answer" && do_mcp_setup=yes - STEP_INDENT=' ' - fi - if [ "$do_mcp_setup" = yes ]; then - # Each client's own CLI does the registering, and Codex's can - # stop to print a sign-in URL, so its output is shown live. - if run_live "Adding the Mapbox MCP servers" "${WORK_DIR}/mcp-install.log" "${WORK_DIR}/mcp-install.json" \ - "${INSTALL_DIR}/mapbox" mcp install --global -o json; then - mcp_ok=yes - else - mcp_ok=no - fi - mcp_grouped "$(cat "${WORK_DIR}/mcp-install.json" 2>/dev/null)" >"${WORK_DIR}/mcp-rows" - # One line per server and outcome, so a server added just now - # reads differently from one that was already there. - mcp_trouble=no - while IFS='|' read -r status what who; do - case "$status" in - installed | already_installed) ;; - *) mcp_trouble=yes ;; - esac - case "$status" in - installed) step_ok "Added ${what} to ${who}" ;; - already_installed) step_skip "${what} was already in ${who}" ;; - installed_login_incomplete) step_warn "Added ${what} to ${who}, but sign-in did not finish" ;; - client_not_found) step_warn "Skipped ${what} for ${who}: CLI not on PATH" ;; - config_unreadable) step_warn "Skipped ${what} for ${who}: config could not be read" ;; - *) step_warn "Could not add ${what} to ${who}" ;; - esac - done <"${WORK_DIR}/mcp-rows" - if [ "$mcp_ok" = no ]; then - # A failure no row above accounts for still gets a line of - # its own, rather than a list of successes. - [ "$mcp_trouble" = yes ] || step_warn "Not every Mapbox MCP server could be added." - step_detail "Run 'mapbox mcp install --global' to see why and try again." - fi + # Most machines have none of these, so most never see this question. + mcp_clients="$(mcp_clients_present)" + if [ -n "$mcp_clients" ]; then + do_mcp_setup=no + if have_tty; then + ask_title "Add the Mapbox MCP servers to ${mcp_clients}?" + ask_note "This runs $(ask_cmd 'mapbox mcp install --global'), now or any time later." + answer="$(ask " Add them now? ${DIM}[y/N]${RESET} ")" || answer='' + is_yes "$answer" && do_mcp_setup=yes + STEP_INDENT=' ' + fi + if [ "$do_mcp_setup" = yes ]; then + # Each client's own CLI does the registering, and Codex's can + # stop to print a sign-in URL, so its output is shown live. + if run_live "Adding the Mapbox MCP servers" "${WORK_DIR}/mcp-install.log" "${WORK_DIR}/mcp-install.json" \ + "${INSTALL_DIR}/mapbox" mcp install --global -o json; then + mcp_ok=yes else - step_skip "Mapbox MCP servers not added to ${mcp_clients}. Run this any time:" - step_detail "${DIM}mapbox mcp install --global${RESET}" + mcp_ok=no fi - STEP_INDENT=' ' + mcp_grouped "$(cat "${WORK_DIR}/mcp-install.json" 2>/dev/null)" >"${WORK_DIR}/mcp-rows" + # One line per server and outcome, so a server added just now + # reads differently from one that was already there. + mcp_trouble=no + while IFS='|' read -r status what who; do + case "$status" in + installed | already_installed) ;; + *) mcp_trouble=yes ;; + esac + case "$status" in + installed) step_ok "Added ${what} to ${who}" ;; + already_installed) step_skip "${what} was already in ${who}" ;; + installed_login_incomplete) step_warn "Added ${what} to ${who}, but sign-in did not finish" ;; + client_not_found) step_warn "Skipped ${what} for ${who}: CLI not on PATH" ;; + config_unreadable) step_warn "Skipped ${what} for ${who}: config could not be read" ;; + *) step_warn "Could not add ${what} to ${who}" ;; + esac + done <"${WORK_DIR}/mcp-rows" + if [ "$mcp_ok" = no ]; then + # A failure no row above accounts for still gets a line of + # its own, rather than a list of successes. + [ "$mcp_trouble" = yes ] || step_warn "Not every Mapbox MCP server could be added." + step_detail "Run 'mapbox mcp install --global' to see why and try again." + fi + else + step_skip "Mapbox MCP servers not added to ${mcp_clients}. Run this any time:" + step_detail "${DIM}mapbox mcp install --global${RESET}" fi + STEP_INDENT=' ' fi fi diff --git a/scripts/test-install.ps1 b/scripts/test-install.ps1 index 6fc77e9..b906ccf 100644 --- a/scripts/test-install.ps1 +++ b/scripts/test-install.ps1 @@ -201,7 +201,8 @@ esac # (ok | already_installed) and MAPBOX_TEST_UPDATE_MODE (clean | dirty, read # only once install reports already_installed) - the same two knobs # test-install.sh's agent-channel fixture uses. MAPBOX_TEST_MCP_MODE (none | -# fresh) drives `mcp`; only its dry run is reachable from here, see below. +# fresh) drives `mcp`, which no case here reaches: install.ps1 runs it only +# after a yes at a console. function New-AgentFakeBinary([string]$Path, [string]$VersionText) { $ErrorActionPreference = 'Continue' if ($OnWindows) { @@ -557,6 +558,14 @@ function New-CaseEnv([string]$Name) { Clear-Env 'MAPBOX_TEST_INSTALL_MODE' Clear-Env 'MAPBOX_TEST_UPDATE_MODE' Clear-Env 'MAPBOX_TEST_MCP_MODE' + # install.ps1 looks MCP clients up on PATH, honoring these, and the + # developer's own clients would otherwise answer. Set-McpClientsOnPath + # points two of them back at something. + $noClient = Join-Path $script:CaseDir 'no-such-client' + $env:MAPBOX_CLAUDE_CLI = $noClient + $env:MAPBOX_CODEX_CLI = $noClient + $env:MAPBOX_CODE_CLI = $noClient + $env:MAPBOX_CURSOR_CLI = $noClient # What install.ps1 reads to decide where it is running. Set explicitly so # the same case means the same thing on Windows and on the machine this is # written on. @@ -565,6 +574,22 @@ function New-CaseEnv([string]$Name) { Clear-Env 'PROCESSOR_ARCHITEW6432' } +# Makes Claude Code and VS Code findable by install.ps1's lookup, through the +# override variables src/mcp.rs also reads. Neither is ever run. +function Set-McpClientsOnPath { + foreach ($pair in @(@('MAPBOX_CLAUDE_CLI', 'claude'), @('MAPBOX_CODE_CLI', 'code'))) { + if ($OnWindows) { + $file = Join-Path $script:CaseDir "$($pair[1]).cmd" + Set-Content -LiteralPath $file -Encoding ASCII -Value '@exit /b 0' + } else { + $file = Join-Path $script:CaseDir $pair[1] + Set-Content -LiteralPath $file -Value "#!/bin/sh`nexit 0" + & chmod 755 $file + } + Set-Item -Path "env:$($pair[0])" -Value $file + } +} + # Unset, never `$env:X = ''`. Windows PowerShell deletes a variable assigned # an empty string; PowerShell 7 keeps it, with an empty value - and an empty # PROCESSOR_ARCHITEW6432 is worse than none, because Windows' WoW64 handling @@ -1071,6 +1096,7 @@ try { New-CaseEnv 'mcp-no-console' $env:MAPBOX_CLI_BASE_URL = "$($open.BaseUrl)/agent-channel" $env:MAPBOX_TEST_MCP_MODE = 'fresh' + Set-McpClientsOnPath Invoke-Installer Expect-Status 0 'exits 0' Expect-NoOut 'Add the Mapbox MCP servers' 'does not ask' @@ -1081,12 +1107,21 @@ try { New-CaseEnv 'mcp-opt-out' $env:MAPBOX_CLI_BASE_URL = "$($open.BaseUrl)/agent-channel" $env:MAPBOX_TEST_MCP_MODE = 'fresh' + Set-McpClientsOnPath $env:MAPBOX_CLI_NO_AGENT_SETUP = '1' Invoke-Installer Expect-Status 0 'exits 0' # Not just 'MCP': -like ignores case, and this case's own directory, # printed in the PATH note above, is named mcp-opt-out. Expect-NoOut 'Mapbox MCP servers' 'says nothing about it' + + Start-Case 'MCP, no client CLI on PATH: nothing said' + New-CaseEnv 'mcp-no-client' + $env:MAPBOX_CLI_BASE_URL = "$($open.BaseUrl)/agent-channel" + $env:MAPBOX_TEST_MCP_MODE = 'fresh' + Invoke-Installer + Expect-Status 0 'exits 0' + Expect-NoOut 'Mapbox MCP servers' 'says nothing when there is nothing to add to' } finally { if ($open) { Stop-ChannelServer $open } if ($gated) { Stop-ChannelServer $gated } diff --git a/scripts/test-install.sh b/scripts/test-install.sh index 800d811..1646e26 100755 --- a/scripts/test-install.sh +++ b/scripts/test-install.sh @@ -283,9 +283,9 @@ make_channel "${CHANNEL}/v0.1.0-dev.abc1234" v0.1.0-dev.abc1234 # environment variables: MAPBOX_TEST_INSTALL_MODE (ok | already_installed) # and MAPBOX_TEST_UPDATE_MODE (clean | dirty, read only once install # reports already_installed). Defaults match the plain first-install case. -# MAPBOX_TEST_MCP_MODE (none | fresh | registered | fail) drives `mcp`; -# none, no MCP client found, is the default so cases about the skill see -# only the skill's question. +# MAPBOX_TEST_MCP_MODE (none | fresh | registered | fail) drives `mcp`. +# Whether the installer asks about MCP at all depends on which client CLIs +# are on PATH, not on this binary; see mcp_clients_on_path. AGENT_CHANNEL="${ROOT}/agent-channel" mkdir -p "${AGENT_CHANNEL}/latest/build" cat >"${AGENT_CHANNEL}/latest/build/mapbox" <<'EOF' @@ -327,20 +327,14 @@ case "${1:-}" in esac ;; mcp) + : >"${HOME}/.mcp-ran" mcp_mode="${MAPBOX_TEST_MCP_MODE:-none}" if [ "$mcp_mode" = none ]; then echo '{"code":"mcp_client_not_found"}' >&2 exit 1 fi - is_dry=no - for a in "$@"; do [ "$a" = "--dry-run" ] && is_dry=yes; done - if [ "$is_dry" = yes ]; then - if [ "$mcp_mode" = registered ]; then - status=already_installed - else - status=would_install - fi - echo '{"results":[{"client":"claude-code","server":"mapbox","status":"'"$status"'"},{"client":"claude-code","server":"mapbox-devkit","status":"'"$status"'"},{"client":"vscode","server":"mapbox","status":"'"$status"'"}]}' + if [ "$mcp_mode" = registered ]; then + echo '{"results":[{"client":"claude-code","server":"mapbox","status":"already_installed"},{"client":"claude-code","server":"mapbox-devkit","status":"already_installed"},{"client":"vscode","server":"mapbox","status":"already_installed"}]}' elif [ "$mcp_mode" = fail ]; then echo '{"results":[{"client":"claude-code","server":"mapbox","status":"installed"},{"client":"vscode","error":"--add-mcp rejected {\\"name\\":\\"mapbox\\"}","server":"mapbox","status":"failed"}]}' exit 1 @@ -672,9 +666,30 @@ new_case_env() { # case-name # turn every marker case into a failure that looks like the marker broke. unset DISABLE_TELEMETRY MAPBOX_CLI_NO_TELEMETRY unset MAPBOX_CLI_NO_AGENT_SETUP MAPBOX_TEST_INSTALL_MODE MAPBOX_TEST_UPDATE_MODE MAPBOX_TEST_MCP_MODE + # The installer looks MCP clients up on PATH, and SAFE_PATH can hold a real + # one (Homebrew's codex), so each points at nothing unless a case says so. + MAPBOX_CLAUDE_CLI="${CASE_DIR}/no-such-client" + MAPBOX_CODEX_CLI="${CASE_DIR}/no-such-client" + MAPBOX_CODE_CLI="${CASE_DIR}/no-such-client" + MAPBOX_CURSOR_CLI="${CASE_DIR}/no-such-client" + export MAPBOX_CLAUDE_CLI MAPBOX_CODEX_CLI MAPBOX_CODE_CLI MAPBOX_CURSOR_CLI export MAPBOX_CLI_BASE_URL MAPBOX_INSTALL_DIR PATH } +# Puts Claude Code and VS Code on PATH, as far as the installer can tell. The +# fake mapbox answers `mcp` for them; these are never run. +mcp_clients_on_path() { + for c in claude code; do + printf '#!/bin/sh\nexit 0\n' >"${CASE_SHIMS}/${c}" + chmod 755 "${CASE_SHIMS}/${c}" + done + MAPBOX_CLAUDE_CLI=claude + MAPBOX_CODE_CLI=code +} + +# Whether the installer ran `mapbox mcp` at all: the fake marks it in HOME. +mcp_ran() { if [ -f "${HOME}/.mcp-ran" ]; then echo yes; else echo no; fi; } + shim() { # shim-name [name-on-path] cp "${SHIMS}/$1" "${CASE_SHIMS}/${2:-$1}" } @@ -1329,6 +1344,7 @@ export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_CLI_NO_AGENT_SETUP=1 export MAPBOX_TEST_MCP_MODE=fresh +mcp_clients_on_path run_piped && status=0 || status=$? expect_status 0 "$status" 'exits 0' expect_no_out 'coding agent' 'skips the question entirely' @@ -1405,9 +1421,11 @@ new_case_env mcp-no-tty export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_MCP_MODE=fresh +mcp_clients_on_path run_piped && status=0 || status=$? expect_status 0 "$status" 'exits 0 rather than blocking (124 would be a hang)' expect_no_out 'Add the Mapbox MCP servers' 'does not ask' +expect_says "$(mcp_ran)" no 'and never runs mapbox mcp, which is the slow part' expect_out 'Mapbox MCP servers not added to Claude Code, VS Code.' 'names each client once' expect_says "$(grep -c '^ - Mapbox MCP servers not added' "$OUT")" 1 'top level when no question was asked' expect_out 'mapbox mcp install --global' 'gives the manual command' @@ -1417,6 +1435,7 @@ new_case_env mcp-tty-yes export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_MCP_MODE=fresh +mcp_clients_on_path run_interactive "$(printf 'n\ny')" && status=0 || status=$? expect_status 0 "$status" 'exits 0' expect_out 'Add the Mapbox MCP servers to Claude Code, VS Code?' 'asks, separately from the skill' @@ -1460,25 +1479,53 @@ new_case_env mcp-tty-no export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_MCP_MODE=fresh +mcp_clients_on_path run_interactive "$(printf 'n\nn')" && status=0 || status=$? expect_status 0 "$status" 'a declined setup is not an install failure' expect_out 'Mapbox MCP servers not added to Claude Code, VS Code.' 'says so' expect_no_out 'Added the Mapbox MCP servers' 'adds nothing' +expect_says "$(mcp_ran)" no 'and never runs mapbox mcp' + +start 'MCP, no client CLI on PATH: not asked, nothing said' +new_case_env mcp-no-client +export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" +export MAPBOX_INSTALL_TILESETS=no +export MAPBOX_TEST_MCP_MODE=fresh +run_interactive n && status=0 || status=$? +expect_status 0 "$status" 'exits 0' +expect_no_out 'MCP' 'says nothing when there is nothing to add to' +expect_says "$(mcp_ran)" no 'and never runs mapbox mcp' -start 'MCP, everything already registered: not asked again' +start 'MCP, a client named by its override variable counts' +new_case_env mcp-override +export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" +export MAPBOX_INSTALL_TILESETS=no +export MAPBOX_TEST_MCP_MODE=fresh +printf '#!/bin/sh\nexit 0\n' >"${CASE_DIR}/my-cursor" +chmod 755 "${CASE_DIR}/my-cursor" +MAPBOX_CURSOR_CLI="${CASE_DIR}/my-cursor" +run_interactive "$(printf 'n\nn')" && status=0 || status=$? +expect_status 0 "$status" 'exits 0' +expect_out 'Mapbox MCP servers not added to Cursor.' 'finds it where the variable points, as src/mcp.rs does' + +start 'MCP, everything already registered: asked, and a yes says so' new_case_env mcp-registered export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_MCP_MODE=registered -run_interactive n && status=0 || status=$? +mcp_clients_on_path +run_interactive "$(printf 'n\ny')" && status=0 || status=$? expect_status 0 "$status" 'exits 0' -expect_no_out 'MCP' 'says nothing when there is nothing to add' +expect_out 'Add the Mapbox MCP servers to Claude Code, VS Code?' 'asks: only a yes runs the check' +expect_out 'Mapbox MCP was already in Claude Code, VS Code' 'reports what was already there' +expect_no_out 'Added Mapbox' 'claims to add nothing' start 'MCP, a failed registration is reported, not fatal' new_case_env mcp-fail export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_MCP_MODE=fail +mcp_clients_on_path run_interactive "$(printf 'n\ny')" && status=0 || status=$? expect_status 0 "$status" 'mapbox itself is still installed' expect_out 'Could not add Mapbox MCP to VS Code' 'reports which one failed' @@ -1491,6 +1538,7 @@ new_case_env mcp-dumb export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_MCP_MODE=fresh +mcp_clients_on_path TERM=dumb run_interactive "$(printf 'n\ny')" && status=0 || status=$? expect_status 0 "$status" 'exits 0' expect_out 'Added Mapbox MCP to Claude Code' 'still adds them' diff --git a/src/mcp.rs b/src/mcp.rs index 5b22d92..17f144f 100644 --- a/src/mcp.rs +++ b/src/mcp.rs @@ -899,6 +899,40 @@ mod tests { assert_eq!(flags.len(), CLIENTS.len()); } + /// The installers decide whether to offer `mcp install` by looking each + /// client up on PATH themselves, from a copy of this table, so a client + /// added here and not there would never be offered. + #[test] + fn installers_know_every_mcp_client() { + let read = |path: &str| { + std::fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join(path)) + .unwrap_or_else(|e| panic!("{path}: {e}")) + }; + let sh = read("scripts/install.sh"); + let ps1 = read("scripts/install.ps1"); + for c in CLIENTS { + let row = format!("'{}|{}|{}'", c.binary_env, c.default_binary, c.label); + assert!(sh.contains(&row), "install.sh has no {row}"); + let row = format!( + "Id = '{}'; Var = '{}'; Bin = '{}'", + c.flag, c.binary_env, c.default_binary + ); + assert!(ps1.contains(&row), "install.ps1 has no {row}"); + let label = format!("'{}' = '{}'", c.flag, c.label); + assert!(ps1.contains(&label), "install.ps1 has no {label}"); + } + assert_eq!( + sh.matches("_CLI|").count(), + CLIENTS.len(), + "install.sh lists a client src/mcp.rs doesn't have" + ); + assert_eq!( + ps1.matches("; Var = '").count(), + CLIENTS.len(), + "install.ps1 lists a client src/mcp.rs doesn't have" + ); + } + #[test] fn every_verb_client_has_an_add_arm() { for client in CLIENTS.iter().filter(|c| c.kind == ClientKind::Verb) { From d8a4f088ac1f0b4fceabcb78ac37b799a9752127 Mon Sep 17 00:00:00 2001 From: Mofei Zhu Date: Thu, 22 Oct 2026 14:54:02 +0300 Subject: [PATCH 2/6] Address review: honest skip line, no-rows failure, literal PowerShell lookup - A no (or no terminal) now says the MCP servers were skipped rather than "not added", since nothing checks whether they are already there. - A yes that fails before any client is tried says nothing was added. - install.sh reads the override with eval instead of printenv, which POSIX does not require. - install.ps1 takes an override path literally, with the extensions src/mcp.rs tries, instead of as a wildcard pattern. - The guard reads only the lookup itself in each installer. - README no longer says the question is asked only when something is left to add. The PowerShell fake's dead dry-run output is replaced by a marker the no-console case asserts on. --- README.md | 5 +++-- scripts/install.ps1 | 16 ++++++++++++---- scripts/install.sh | 24 ++++++++++++++++-------- scripts/test-install.ps1 | 32 +++++++++++++------------------- scripts/test-install.sh | 20 ++++++++++++++++---- src/mcp.rs | 25 +++++++++++++++++++++++-- 6 files changed, 83 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 1bdd008..9ed5efa 100644 --- a/README.md +++ b/README.md @@ -106,8 +106,9 @@ it does neither, printing the two commands to run by hand instead. It then asks, separately, whether to add the [Mapbox MCP servers](#mcp-servers) to each coding agent it finds that can take one (Claude Code, Codex, VS Code, -Cursor), the same thing `mapbox mcp install --global` does. It asks only when -there is something left to add, with the same rules: no terminal means no. +Cursor), the same thing `mapbox mcp install --global` does. It asks whenever +one of their CLIs is on `PATH`, even if the servers are already there, with +the same rules: no terminal means no. Set `MAPBOX_CLI_NO_AGENT_SETUP=1` to skip both questions. On macOS and Linux, `install.sh` also offers to install the diff --git a/scripts/install.ps1 b/scripts/install.ps1 index f554082..7c962a7 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -845,7 +845,8 @@ Windows 11 on Arm has and Windows 10 on Arm does not. # install.sh gives: checking what is already registered runs each # client's own CLI, which took seconds with nothing on screen. # `mapbox mcp install` does that check itself once someone says yes, - # so a reinstall asks again and a yes reports what was already there. + # so a reinstall asks again and a yes reports what was already there, + # and a no is reported as skipped rather than "not added". if ($AgentSetupAllowed) { $previousErrorAction = $ErrorActionPreference $ErrorActionPreference = 'Continue' @@ -866,9 +867,16 @@ Windows 11 on Arm has and Windows 10 on Arm does not. @{ Id = 'cursor'; Var = 'MAPBOX_CURSOR_CLI'; Bin = 'cursor' })) { $bin = [Environment]::GetEnvironmentVariable($client.Var) if (-not $bin) { $bin = $client.Bin } - if (Get-Command $bin -CommandType Application -ErrorAction SilentlyContinue) { - $mcpFound += $clientLabels[$client.Id] + if ($bin.Contains('\') -or $bin.Contains('/')) { + # A path is taken literally, with the extensions + # src/mcp.rs also tries on a name that has none. + $hit = @('', '.cmd', '.bat', '.exe') | + Where-Object { Test-Path -LiteralPath "$bin$_" -PathType Leaf } | + Select-Object -First 1 + } else { + $hit = Get-Command ([WildcardPattern]::Escape($bin)) -CommandType Application -ErrorAction SilentlyContinue } + if ($hit) { $mcpFound += $clientLabels[$client.Id] } } $mcpClients = $mcpFound -join ', ' if ($mcpClients) { @@ -923,7 +931,7 @@ Windows 11 on Arm has and Windows 10 on Arm does not. Write-Detail "Run 'mapbox mcp install --global' to see why and try again." } } else { - Write-Skip "Mapbox MCP servers not added to $mcpClients. Run this any time:" + Write-Skip "Skipped the Mapbox MCP servers for $mcpClients. Run this any time:" Write-Detail 'mapbox mcp install --global' } $Layout.Indent = ' ' diff --git a/scripts/install.sh b/scripts/install.sh index 195da4a..6c4fd11 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1016,11 +1016,12 @@ fi # Whether to ask is a PATH lookup and nothing more. Checking what is already # registered means running each client's own CLI, and Claude Code's takes over # a second per call, so the screen sat still for several seconds between the -# two questions, and for anyone who answers no or has no terminal that wait -# bought nothing. `mapbox mcp install` does that check itself once someone -# says yes, and never replaces an entry that is already there (src/mcp.rs). A -# reinstall therefore asks again even when everything is registered, and a -# yes then reports each server as already there. +# two questions, even for anyone who answers no or has no terminal. +# `mapbox mcp install` does that check itself once someone says yes, and never +# replaces an entry that is already there (src/mcp.rs). A reinstall therefore +# asks again even when everything is registered, and a yes then reports each +# server as already there. Without that check nothing here knows whether a +# server is missing, so a no is reported as skipped, not as "not added". # "status|server|client" per row of `mapbox mcp install -o json`, with the # labels src/mcp.rs prints in place of its ids. @@ -1066,7 +1067,8 @@ mcp_clients_present() { mcp_var="${mcp_client%%|*}" mcp_rest="${mcp_client#*|}" mcp_label="${mcp_rest#*|}" - mcp_bin="$(printenv "$mcp_var" || true)" + mcp_bin='' + eval "mcp_bin=\${${mcp_var}:-}" [ -n "$mcp_bin" ] || mcp_bin="${mcp_rest%%|*}" if command -v "$mcp_bin" >/dev/null 2>&1; then printf '%s\n' "$mcp_label" @@ -1116,11 +1118,17 @@ if agent_setup_allowed; then if [ "$mcp_ok" = no ]; then # A failure no row above accounts for still gets a line of # its own, rather than a list of successes. - [ "$mcp_trouble" = yes ] || step_warn "Not every Mapbox MCP server could be added." + # No rows at all is a failure before any client was tried, + # such as one found on PATH that mapbox could not run. + if [ ! -s "${WORK_DIR}/mcp-rows" ]; then + step_warn "Could not add the Mapbox MCP servers." + elif [ "$mcp_trouble" = no ]; then + step_warn "Not every Mapbox MCP server could be added." + fi step_detail "Run 'mapbox mcp install --global' to see why and try again." fi else - step_skip "Mapbox MCP servers not added to ${mcp_clients}. Run this any time:" + step_skip "Skipped the Mapbox MCP servers for ${mcp_clients}. Run this any time:" step_detail "${DIM}mapbox mcp install --global${RESET}" fi STEP_INDENT=' ' diff --git a/scripts/test-install.ps1 b/scripts/test-install.ps1 index b906ccf..a77098e 100644 --- a/scripts/test-install.ps1 +++ b/scripts/test-install.ps1 @@ -200,9 +200,9 @@ esac # variables so the same binary covers every branch: MAPBOX_TEST_INSTALL_MODE # (ok | already_installed) and MAPBOX_TEST_UPDATE_MODE (clean | dirty, read # only once install reports already_installed) - the same two knobs -# test-install.sh's agent-channel fixture uses. MAPBOX_TEST_MCP_MODE (none | -# fresh) drives `mcp`, which no case here reaches: install.ps1 runs it only -# after a yes at a console. +# test-install.sh's agent-channel fixture uses. `mcp` only marks that it ran, +# in the file MAPBOX_TEST_MCP_RAN names: install.ps1 should run it only after +# a yes at a console, which no case here can give. function New-AgentFakeBinary([string]$Path, [string]$VersionText) { $ErrorActionPreference = 'Continue' if ($OnWindows) { @@ -253,12 +253,10 @@ class Program { } } if (a0 == "mcp") { - if ((Environment.GetEnvironmentVariable("MAPBOX_TEST_MCP_MODE") ?? "none") == "none") { - Console.Error.WriteLine("{\"code\":\"mcp_client_not_found\"}"); - return 1; - } - Console.WriteLine("{\"results\":[{\"client\":\"claude-code\",\"server\":\"mapbox\",\"status\":\"would_install\"},{\"client\":\"claude-code\",\"server\":\"mapbox-devkit\",\"status\":\"would_install\"},{\"client\":\"vscode\",\"server\":\"mapbox\",\"status\":\"would_install\"}]}"); - return 0; + string marker = Environment.GetEnvironmentVariable("MAPBOX_TEST_MCP_RAN"); + if (!string.IsNullOrEmpty(marker)) { System.IO.File.WriteAllText(marker, ""); } + Console.Error.WriteLine("{\"code\":\"mcp_client_not_found\"}"); + return 1; } Console.WriteLine("$VersionText"); return 0; @@ -283,11 +281,9 @@ case "`${1:-}" in echo '{"written":true}' ;; mcp) - if [ "`${MAPBOX_TEST_MCP_MODE:-none}" = none ]; then - echo '{"code":"mcp_client_not_found"}' >&2 - exit 1 - fi - echo '{"results":[{"client":"claude-code","server":"mapbox","status":"would_install"},{"client":"claude-code","server":"mapbox-devkit","status":"would_install"},{"client":"vscode","server":"mapbox","status":"would_install"}]}' + [ -z "`${MAPBOX_TEST_MCP_RAN:-}" ] || : >"`$MAPBOX_TEST_MCP_RAN" + echo '{"code":"mcp_client_not_found"}' >&2 + exit 1 ;; agent-skills) sub="`$2" @@ -557,7 +553,7 @@ function New-CaseEnv([string]$Name) { Clear-Env 'MAPBOX_CLI_NO_AGENT_SETUP' Clear-Env 'MAPBOX_TEST_INSTALL_MODE' Clear-Env 'MAPBOX_TEST_UPDATE_MODE' - Clear-Env 'MAPBOX_TEST_MCP_MODE' + $env:MAPBOX_TEST_MCP_RAN = Join-Path $script:CaseDir 'mcp-ran' # install.ps1 looks MCP clients up on PATH, honoring these, and the # developer's own clients would otherwise answer. Set-McpClientsOnPath # points two of them back at something. @@ -1095,18 +1091,17 @@ try { Start-Case 'MCP, no console: not asked, not added' New-CaseEnv 'mcp-no-console' $env:MAPBOX_CLI_BASE_URL = "$($open.BaseUrl)/agent-channel" - $env:MAPBOX_TEST_MCP_MODE = 'fresh' Set-McpClientsOnPath Invoke-Installer Expect-Status 0 'exits 0' Expect-NoOut 'Add the Mapbox MCP servers' 'does not ask' - Expect-Out 'Mapbox MCP servers not added to Claude Code, VS Code.' 'names each client once' + Expect-Out 'Skipped the Mapbox MCP servers for Claude Code, VS Code.' 'names each client once' + Expect-Equal 'False' "$(Test-Path -LiteralPath $env:MAPBOX_TEST_MCP_RAN)" 'and never runs mapbox mcp, which is the slow part' Expect-Out 'mapbox mcp install --global' 'gives the manual command' Start-Case 'MAPBOX_CLI_NO_AGENT_SETUP skips the MCP question too' New-CaseEnv 'mcp-opt-out' $env:MAPBOX_CLI_BASE_URL = "$($open.BaseUrl)/agent-channel" - $env:MAPBOX_TEST_MCP_MODE = 'fresh' Set-McpClientsOnPath $env:MAPBOX_CLI_NO_AGENT_SETUP = '1' Invoke-Installer @@ -1118,7 +1113,6 @@ try { Start-Case 'MCP, no client CLI on PATH: nothing said' New-CaseEnv 'mcp-no-client' $env:MAPBOX_CLI_BASE_URL = "$($open.BaseUrl)/agent-channel" - $env:MAPBOX_TEST_MCP_MODE = 'fresh' Invoke-Installer Expect-Status 0 'exits 0' Expect-NoOut 'Mapbox MCP servers' 'says nothing when there is nothing to add to' diff --git a/scripts/test-install.sh b/scripts/test-install.sh index 1646e26..fbec674 100755 --- a/scripts/test-install.sh +++ b/scripts/test-install.sh @@ -1426,8 +1426,8 @@ run_piped && status=0 || status=$? expect_status 0 "$status" 'exits 0 rather than blocking (124 would be a hang)' expect_no_out 'Add the Mapbox MCP servers' 'does not ask' expect_says "$(mcp_ran)" no 'and never runs mapbox mcp, which is the slow part' -expect_out 'Mapbox MCP servers not added to Claude Code, VS Code.' 'names each client once' -expect_says "$(grep -c '^ - Mapbox MCP servers not added' "$OUT")" 1 'top level when no question was asked' +expect_out 'Skipped the Mapbox MCP servers for Claude Code, VS Code.' 'names each client once' +expect_says "$(grep -c '^ - Skipped the Mapbox MCP servers' "$OUT")" 1 'top level when no question was asked' expect_out 'mapbox mcp install --global' 'gives the manual command' start 'MCP, a terminal, answered yes' @@ -1482,7 +1482,7 @@ export MAPBOX_TEST_MCP_MODE=fresh mcp_clients_on_path run_interactive "$(printf 'n\nn')" && status=0 || status=$? expect_status 0 "$status" 'a declined setup is not an install failure' -expect_out 'Mapbox MCP servers not added to Claude Code, VS Code.' 'says so' +expect_out 'Skipped the Mapbox MCP servers for Claude Code, VS Code.' 'says so' expect_no_out 'Added the Mapbox MCP servers' 'adds nothing' expect_says "$(mcp_ran)" no 'and never runs mapbox mcp' @@ -1506,7 +1506,7 @@ chmod 755 "${CASE_DIR}/my-cursor" MAPBOX_CURSOR_CLI="${CASE_DIR}/my-cursor" run_interactive "$(printf 'n\nn')" && status=0 || status=$? expect_status 0 "$status" 'exits 0' -expect_out 'Mapbox MCP servers not added to Cursor.' 'finds it where the variable points, as src/mcp.rs does' +expect_out 'Skipped the Mapbox MCP servers for Cursor.' 'finds it where the variable points, as src/mcp.rs does' start 'MCP, everything already registered: asked, and a yes says so' new_case_env mcp-registered @@ -1520,6 +1520,18 @@ expect_out 'Add the Mapbox MCP servers to Claude Code, VS Code?' 'asks: only a y expect_out 'Mapbox MCP was already in Claude Code, VS Code' 'reports what was already there' expect_no_out 'Added Mapbox' 'claims to add nothing' +start 'MCP, a client on PATH that mapbox cannot run: a yes reports the failure' +new_case_env mcp-not-detected +export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" +export MAPBOX_INSTALL_TILESETS=no +export MAPBOX_TEST_MCP_MODE=none +mcp_clients_on_path +run_interactive "$(printf 'n\ny')" && status=0 || status=$? +expect_status 0 "$status" 'mapbox itself is still installed' +expect_out 'Could not add the Mapbox MCP servers.' 'says nothing was added' +expect_no_out 'Not every Mapbox MCP server' 'rather than implying some were' +expect_out "Run 'mapbox mcp install --global' to see why and try again." 'points at the command' + start 'MCP, a failed registration is reported, not fatal' new_case_env mcp-fail export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" diff --git a/src/mcp.rs b/src/mcp.rs index 17f144f..ab05b50 100644 --- a/src/mcp.rs +++ b/src/mcp.rs @@ -908,8 +908,29 @@ mod tests { std::fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join(path)) .unwrap_or_else(|e| panic!("{path}: {e}")) }; - let sh = read("scripts/install.sh"); - let ps1 = read("scripts/install.ps1"); + // Only the lookup itself, so a row left in a comment or a matching + // string elsewhere in the script can't stand in for it. + let between = |text: &str, path: &str, start: &str, end: &str| -> String { + let from = text + .find(start) + .unwrap_or_else(|| panic!("{path} has no {start}")); + let len = text[from..] + .find(end) + .unwrap_or_else(|| panic!("{path} has no {end}")); + text[from..from + len].to_string() + }; + let sh = between( + &read("scripts/install.sh"), + "install.sh", + "mcp_clients_present() {", + "\n}\n", + ); + let ps1 = between( + &read("scripts/install.ps1"), + "install.ps1", + "$clientLabels = @{", + "$mcpClients = $mcpFound -join", + ); for c in CLIENTS { let row = format!("'{}|{}|{}'", c.binary_env, c.default_binary, c.label); assert!(sh.contains(&row), "install.sh has no {row}"); From aa90464879a0c7ce9b72b8f1e702d70a5440dbe3 Mon Sep 17 00:00:00 2001 From: Mofei Zhu Date: Thu, 22 Oct 2026 15:13:35 +0300 Subject: [PATCH 3/6] Drop the installer's own MCP client lookup Instead of looking clients up on PATH, the installer now asks the MCP question whenever the skill step found a coding agent, and leaves finding clients to `mapbox mcp install` after a yes. That removes the copied client table and its guard test, and the mismatch between the installer's lookup and the CLI's. When the agent found takes skills but no client mapbox can register with (Goose, Amp), a yes now says there was nothing to add them to instead of reporting a failure. install.ps1 keeps stderr live while capturing it, to tell that case apart. --- CHANGELOG.md | 9 ++- README.md | 8 +- scripts/install.ps1 | 156 +++++++++++++++++++-------------------- scripts/install.sh | 137 +++++++++++++++------------------- scripts/test-install.ps1 | 35 +-------- scripts/test-install.sh | 82 +++++--------------- src/mcp.rs | 55 -------------- 7 files changed, 162 insertions(+), 320 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a98167..df9c6b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,10 +23,11 @@ that may never merge. They are not releases and are not listed here. ## Unreleased - `install.sh`/`install.ps1`: the MCP question no longer waits several - seconds before it appears. The installer now asks whenever a supported - coding agent's CLI is on `PATH`, and only checks what is already - registered after a yes. A reinstall therefore asks again even when every - server is registered; a yes then reports each one as already there. + seconds before it appears. The installer asks whenever it found a coding + agent, and only after a yes does it look for Claude Code, Codex, VS Code + and Cursor and check what each already has. A reinstall therefore asks + again even when every server is registered; a yes then reports each one + as already there. ## 1.1.0 - 2026-10-22 diff --git a/README.md b/README.md index 9ed5efa..a73a59a 100644 --- a/README.md +++ b/README.md @@ -105,10 +105,10 @@ or run the script somewhere with no terminal to ask on (CI, a container), and it does neither, printing the two commands to run by hand instead. It then asks, separately, whether to add the [Mapbox MCP servers](#mcp-servers) -to each coding agent it finds that can take one (Claude Code, Codex, VS Code, -Cursor), the same thing `mapbox mcp install --global` does. It asks whenever -one of their CLIs is on `PATH`, even if the servers are already there, with -the same rules: no terminal means no. +to your coding agents, the same thing `mapbox mcp install --global` does: a +yes adds them to whichever of Claude Code, Codex, VS Code and Cursor are +installed, and skips any that already have them. It asks whenever it found a +coding agent above, with the same rules: no terminal means no. Set `MAPBOX_CLI_NO_AGENT_SETUP=1` to skip both questions. On macOS and Linux, `install.sh` also offers to install the diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 7c962a7..de126fe 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -751,6 +751,7 @@ Windows 11 on Arm has and Windows 10 on Arm does not. ) } + $agentsFound = $false if ($AgentSetupAllowed) { # A child writing to stderr is a terminating NativeCommandError in # Windows PowerShell under $ErrorActionPreference = 'Stop' (set @@ -765,6 +766,7 @@ Windows 11 on Arm has and Windows 10 on Arm does not. try { $checkOut = (& $destination generate-skills --global --dry-run -o json 2>$null) -join '' if ($LASTEXITCODE -eq 0) { + $agentsFound = $true $agentNames = Get-AgentName $checkOut Write-Ok "Coding agents found: $agentNames." $doAgentSetup = $false @@ -838,16 +840,17 @@ Windows 11 on Arm has and Windows 10 on Arm does not. # # A separate question from the skill above: this registers a hosted # server in the coding agent's own config, next to servers the user - # added, rather than writing a directory this CLI owns. Same opt-out - # and same no-console rule as the skill. + # added, rather than writing a directory this CLI owns. Asked only + # when the skill step above found a coding agent, with the same + # no-console rule. # - # Whether to ask is a PATH lookup and nothing more, for the reason - # install.sh gives: checking what is already registered runs each - # client's own CLI, which took seconds with nothing on screen. - # `mapbox mcp install` does that check itself once someone says yes, - # so a reinstall asks again and a yes reports what was already there, - # and a no is reported as skipped rather than "not added". - if ($AgentSetupAllowed) { + # Nothing here checks which clients are installed or what they + # already have, for the reason install.sh gives: that check runs each + # client's own CLI and took seconds with nothing on screen. `mapbox + # mcp install` tries each client itself once someone says yes, so a + # reinstall asks again, a yes reports what was already there, and a + # no is reported as skipped. + if ($agentsFound) { $previousErrorAction = $ErrorActionPreference $ErrorActionPreference = 'Continue' try { @@ -855,87 +858,76 @@ Windows 11 on Arm has and Windows 10 on Arm does not. 'claude-code' = 'Claude Code'; 'codex' = 'Codex' 'vscode' = 'VS Code'; 'cursor' = 'Cursor' } - # One row per client in src/mcp.rs's CLIENTS, honoring the - # same override variable it does; - # `installers_know_every_mcp_client` there holds this list to - # that table. Most machines have none of these. - $mcpFound = @() - foreach ($client in @( - @{ Id = 'claude-code'; Var = 'MAPBOX_CLAUDE_CLI'; Bin = 'claude' }, - @{ Id = 'codex'; Var = 'MAPBOX_CODEX_CLI'; Bin = 'codex' }, - @{ Id = 'vscode'; Var = 'MAPBOX_CODE_CLI'; Bin = 'code' }, - @{ Id = 'cursor'; Var = 'MAPBOX_CURSOR_CLI'; Bin = 'cursor' })) { - $bin = [Environment]::GetEnvironmentVariable($client.Var) - if (-not $bin) { $bin = $client.Bin } - if ($bin.Contains('\') -or $bin.Contains('/')) { - # A path is taken literally, with the extensions - # src/mcp.rs also tries on a name that has none. - $hit = @('', '.cmd', '.bat', '.exe') | - Where-Object { Test-Path -LiteralPath "$bin$_" -PathType Leaf } | - Select-Object -First 1 - } else { - $hit = Get-Command ([WildcardPattern]::Escape($bin)) -CommandType Application -ErrorAction SilentlyContinue - } - if ($hit) { $mcpFound += $clientLabels[$client.Id] } + $doMcpSetup = $false + if (-not [Console]::IsInputRedirected) { + Write-AskTitle 'Add the Mapbox MCP servers to your coding agents?' + Write-AskNote @('This runs ', 'mapbox mcp install --global', ', now or any time later.') + $answer = Read-AskAnswer 'Add them now?' + $Layout.Indent = ' ' + if ($answer -match '^(?i:y|yes)$') { $doMcpSetup = $true } } - $mcpClients = $mcpFound -join ', ' - if ($mcpClients) { - $doMcpSetup = $false - if (-not [Console]::IsInputRedirected) { - Write-AskTitle "Add the Mapbox MCP servers to ${mcpClients}?" - Write-AskNote @('This runs ', 'mapbox mcp install --global', ', now or any time later.') - $answer = Read-AskAnswer 'Add them now?' - $Layout.Indent = ' ' - if ($answer -match '^(?i:y|yes)$') { $doMcpSetup = $true } + if ($doMcpSetup) { + # Each client's own CLI does the registering, and + # Codex's can stop to print a sign-in URL, so + # stderr - where mapbox forwards what they print - + # is left on the console, live. No spinner: a + # second writer to the console is not worth it + # here without a terminal to test it on. + Write-Part $Layout.Indent + Write-Part '...' 'Cyan' + Write-Host ' Adding the Mapbox MCP servers' + # stderr is passed through as it arrives and kept + # too, for the error code a failure carries. + $mcpErr = '' + $mcpOut = (& $destination mcp install --global -o json 2>&1 | ForEach-Object { + if ($_ -is [System.Management.Automation.ErrorRecord]) { + $line = $_.ToString() + [Console]::Error.WriteLine($line) + $mcpErr += $line + } else { $_ } + }) -join '' + $mcpOk = $LASTEXITCODE -eq 0 + # One line per server and outcome, clients joined, + # so a server added just now reads differently from + # one that was already there. + $serverLabels = @{ 'mapbox' = 'Mapbox MCP'; 'mapbox-devkit' = 'Mapbox DevKit MCP' } + $rows = @() + try { $rows = @(($mcpOut | ConvertFrom-Json).results) } catch { $rows = @() } + $groups = [ordered]@{} + foreach ($row in $rows) { + $what = if ($serverLabels.ContainsKey($row.server)) { $serverLabels[$row.server] } else { $row.server } + $who = if ($clientLabels.ContainsKey($row.client)) { $clientLabels[$row.client] } else { $row.client } + $key = "$($row.status)|$what" + if (-not $groups.Contains($key)) { $groups[$key] = @() } + $groups[$key] += $who } - if ($doMcpSetup) { - # Each client's own CLI does the registering, and - # Codex's can stop to print a sign-in URL, so - # stderr - where mapbox forwards what they print - - # is left on the console, live. No spinner: a - # second writer to the console is not worth it - # here without a terminal to test it on. - Write-Part $Layout.Indent - Write-Part '...' 'Cyan' - Write-Host ' Adding the Mapbox MCP servers' - $mcpOut = (& $destination mcp install --global -o json) -join '' - $mcpOk = $LASTEXITCODE -eq 0 - # One line per server and outcome, clients joined, - # so a server added just now reads differently from - # one that was already there. - $serverLabels = @{ 'mapbox' = 'Mapbox MCP'; 'mapbox-devkit' = 'Mapbox DevKit MCP' } - $rows = @() - try { $rows = @(($mcpOut | ConvertFrom-Json).results) } catch { $rows = @() } - $groups = [ordered]@{} - foreach ($row in $rows) { - $what = if ($serverLabels.ContainsKey($row.server)) { $serverLabels[$row.server] } else { $row.server } - $who = if ($clientLabels.ContainsKey($row.client)) { $clientLabels[$row.client] } else { $row.client } - $key = "$($row.status)|$what" - if (-not $groups.Contains($key)) { $groups[$key] = @() } - $groups[$key] += $who + foreach ($key in $groups.Keys) { + $status, $what = $key -split '\|', 2 + $who = $groups[$key] -join ', ' + switch ($status) { + 'installed' { Write-Ok "Added $what to $who" } + 'already_installed' { Write-Skip "$what was already in $who" } + 'installed_login_incomplete' { Write-Warn "Added $what to $who, but sign-in did not finish" } + 'client_not_found' { Write-Warn "Skipped $what for ${who}: CLI not on PATH" } + 'config_unreadable' { Write-Warn "Skipped $what for ${who}: config could not be read" } + default { Write-Warn "Could not add $what to $who" } } - foreach ($key in $groups.Keys) { - $status, $what = $key -split '\|', 2 - $who = $groups[$key] -join ', ' - switch ($status) { - 'installed' { Write-Ok "Added $what to $who" } - 'already_installed' { Write-Skip "$what was already in $who" } - 'installed_login_incomplete' { Write-Warn "Added $what to $who, but sign-in did not finish" } - 'client_not_found' { Write-Warn "Skipped $what for ${who}: CLI not on PATH" } - 'config_unreadable' { Write-Warn "Skipped $what for ${who}: config could not be read" } - default { Write-Warn "Could not add $what to $who" } - } - } - if (-not $mcpOk) { + } + if (-not $mcpOk) { + if ($mcpErr -like '*"code":"mcp_client_not_found"*') { + # The agent found above takes skills but not + # an MCP server this CLI can register. + Write-Skip 'No Claude Code, Codex, VS Code or Cursor here to add them to.' + } else { if ($rows.Count -eq 0) { Write-Warn 'Could not add the Mapbox MCP servers.' } Write-Detail "Run 'mapbox mcp install --global' to see why and try again." } - } else { - Write-Skip "Skipped the Mapbox MCP servers for $mcpClients. Run this any time:" - Write-Detail 'mapbox mcp install --global' } - $Layout.Indent = ' ' + } else { + Write-Skip 'Skipped the Mapbox MCP servers. Run this any time:' + Write-Detail 'mapbox mcp install --global' } + $Layout.Indent = ' ' } finally { $ErrorActionPreference = $previousErrorAction } diff --git a/scripts/install.sh b/scripts/install.sh index 6c4fd11..4f1f114 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -943,8 +943,10 @@ agent_names_from() { # agent_names_from : comma-joined agent labels paste -sd, - | sed 's/,/, /g' } +agents_found=no if agent_setup_allowed; then if agent_check=$(log_capture "${INSTALL_DIR}/mapbox" generate-skills --global --dry-run -o json); then + agents_found=yes agent_names="$(agent_names_from "$agent_check")" step_ok "Coding agents found: ${agent_names}." do_agent_setup=no @@ -1010,18 +1012,17 @@ fi # # A separate question from the skill above: this registers a hosted server in # the coding agent's own config, next to servers the user added, rather than -# writing a directory this CLI owns. Same opt-out and same no-terminal rule as -# the skill. +# writing a directory this CLI owns. Asked only when the skill step above +# found a coding agent, with the same opt-out and no-terminal rule. # -# Whether to ask is a PATH lookup and nothing more. Checking what is already -# registered means running each client's own CLI, and Claude Code's takes over -# a second per call, so the screen sat still for several seconds between the -# two questions, even for anyone who answers no or has no terminal. -# `mapbox mcp install` does that check itself once someone says yes, and never -# replaces an entry that is already there (src/mcp.rs). A reinstall therefore -# asks again even when everything is registered, and a yes then reports each -# server as already there. Without that check nothing here knows whether a -# server is missing, so a no is reported as skipped, not as "not added". +# Nothing here checks which clients are installed or what they already have. +# That check runs each client's own CLI, and Claude Code's takes over a second +# per call, so it held the screen still for several seconds between the two +# questions, even for anyone who then answered no. `mapbox mcp install` tries +# each client itself once someone says yes: it skips one that isn't there and +# never replaces an entry that is (src/mcp.rs). So a reinstall asks again even +# when everything is registered, a yes reports each server as already there, +# and a no is reported as skipped, since nothing knows whether it was missing. # "status|server|client" per row of `mapbox mcp install -o json`, with the # labels src/mcp.rs prints in place of its ids. @@ -1054,72 +1055,50 @@ mcp_grouped() { # mcp_grouped } END { for (i = 1; i <= n; i++) print order[i] "|" who[order[i]] }' } -# The MCP clients whose CLI is on PATH, comma-joined. One row per client in -# src/mcp.rs's CLIENTS, as "override variable|binary|label", honoring the same -# override variable it does; `installers_know_every_mcp_client` there holds -# this list to that table. -mcp_clients_present() { - for mcp_client in \ - 'MAPBOX_CLAUDE_CLI|claude|Claude Code' \ - 'MAPBOX_CODEX_CLI|codex|Codex' \ - 'MAPBOX_CODE_CLI|code|VS Code' \ - 'MAPBOX_CURSOR_CLI|cursor|Cursor'; do - mcp_var="${mcp_client%%|*}" - mcp_rest="${mcp_client#*|}" - mcp_label="${mcp_rest#*|}" - mcp_bin='' - eval "mcp_bin=\${${mcp_var}:-}" - [ -n "$mcp_bin" ] || mcp_bin="${mcp_rest%%|*}" - if command -v "$mcp_bin" >/dev/null 2>&1; then - printf '%s\n' "$mcp_label" - fi - done | paste -sd, - | sed 's/,/, /g' -} - -if agent_setup_allowed; then - # Most machines have none of these, so most never see this question. - mcp_clients="$(mcp_clients_present)" - if [ -n "$mcp_clients" ]; then - do_mcp_setup=no - if have_tty; then - ask_title "Add the Mapbox MCP servers to ${mcp_clients}?" - ask_note "This runs $(ask_cmd 'mapbox mcp install --global'), now or any time later." - answer="$(ask " Add them now? ${DIM}[y/N]${RESET} ")" || answer='' - is_yes "$answer" && do_mcp_setup=yes - STEP_INDENT=' ' +if [ "$agents_found" = yes ]; then + do_mcp_setup=no + if have_tty; then + ask_title "Add the Mapbox MCP servers to your coding agents?" + ask_note "This runs $(ask_cmd 'mapbox mcp install --global'), now or any time later." + answer="$(ask " Add them now? ${DIM}[y/N]${RESET} ")" || answer='' + is_yes "$answer" && do_mcp_setup=yes + STEP_INDENT=' ' + fi + if [ "$do_mcp_setup" = yes ]; then + # Each client's own CLI does the registering, and Codex's can + # stop to print a sign-in URL, so its output is shown live. + if run_live "Adding the Mapbox MCP servers" "${WORK_DIR}/mcp-install.log" "${WORK_DIR}/mcp-install.json" \ + "${INSTALL_DIR}/mapbox" mcp install --global -o json; then + mcp_ok=yes + else + mcp_ok=no fi - if [ "$do_mcp_setup" = yes ]; then - # Each client's own CLI does the registering, and Codex's can - # stop to print a sign-in URL, so its output is shown live. - if run_live "Adding the Mapbox MCP servers" "${WORK_DIR}/mcp-install.log" "${WORK_DIR}/mcp-install.json" \ - "${INSTALL_DIR}/mapbox" mcp install --global -o json; then - mcp_ok=yes + mcp_grouped "$(cat "${WORK_DIR}/mcp-install.json" 2>/dev/null)" >"${WORK_DIR}/mcp-rows" + # One line per server and outcome, so a server added just now + # reads differently from one that was already there. + mcp_trouble=no + while IFS='|' read -r status what who; do + case "$status" in + installed | already_installed) ;; + *) mcp_trouble=yes ;; + esac + case "$status" in + installed) step_ok "Added ${what} to ${who}" ;; + already_installed) step_skip "${what} was already in ${who}" ;; + installed_login_incomplete) step_warn "Added ${what} to ${who}, but sign-in did not finish" ;; + client_not_found) step_warn "Skipped ${what} for ${who}: CLI not on PATH" ;; + config_unreadable) step_warn "Skipped ${what} for ${who}: config could not be read" ;; + *) step_warn "Could not add ${what} to ${who}" ;; + esac + done <"${WORK_DIR}/mcp-rows" + if [ "$mcp_ok" = no ]; then + if grep -qs '"code":"mcp_client_not_found"' "${WORK_DIR}/mcp-install.log" "${WORK_DIR}/mcp-install.json"; then + # The agent found above takes skills but not an MCP + # server this CLI can register (Goose, Amp, ...). + step_skip "No Claude Code, Codex, VS Code or Cursor here to add them to." else - mcp_ok=no - fi - mcp_grouped "$(cat "${WORK_DIR}/mcp-install.json" 2>/dev/null)" >"${WORK_DIR}/mcp-rows" - # One line per server and outcome, so a server added just now - # reads differently from one that was already there. - mcp_trouble=no - while IFS='|' read -r status what who; do - case "$status" in - installed | already_installed) ;; - *) mcp_trouble=yes ;; - esac - case "$status" in - installed) step_ok "Added ${what} to ${who}" ;; - already_installed) step_skip "${what} was already in ${who}" ;; - installed_login_incomplete) step_warn "Added ${what} to ${who}, but sign-in did not finish" ;; - client_not_found) step_warn "Skipped ${what} for ${who}: CLI not on PATH" ;; - config_unreadable) step_warn "Skipped ${what} for ${who}: config could not be read" ;; - *) step_warn "Could not add ${what} to ${who}" ;; - esac - done <"${WORK_DIR}/mcp-rows" - if [ "$mcp_ok" = no ]; then - # A failure no row above accounts for still gets a line of - # its own, rather than a list of successes. - # No rows at all is a failure before any client was tried, - # such as one found on PATH that mapbox could not run. + # A failure no row above accounts for still gets a line + # of its own, rather than a list of successes. if [ ! -s "${WORK_DIR}/mcp-rows" ]; then step_warn "Could not add the Mapbox MCP servers." elif [ "$mcp_trouble" = no ]; then @@ -1127,12 +1106,12 @@ if agent_setup_allowed; then fi step_detail "Run 'mapbox mcp install --global' to see why and try again." fi - else - step_skip "Skipped the Mapbox MCP servers for ${mcp_clients}. Run this any time:" - step_detail "${DIM}mapbox mcp install --global${RESET}" fi - STEP_INDENT=' ' + else + step_skip "Skipped the Mapbox MCP servers. Run this any time:" + step_detail "${DIM}mapbox mcp install --global${RESET}" fi + STEP_INDENT=' ' fi # --- The Tilesets CLI ------------------------------------------------------ diff --git a/scripts/test-install.ps1 b/scripts/test-install.ps1 index a77098e..9231515 100644 --- a/scripts/test-install.ps1 +++ b/scripts/test-install.ps1 @@ -554,14 +554,6 @@ function New-CaseEnv([string]$Name) { Clear-Env 'MAPBOX_TEST_INSTALL_MODE' Clear-Env 'MAPBOX_TEST_UPDATE_MODE' $env:MAPBOX_TEST_MCP_RAN = Join-Path $script:CaseDir 'mcp-ran' - # install.ps1 looks MCP clients up on PATH, honoring these, and the - # developer's own clients would otherwise answer. Set-McpClientsOnPath - # points two of them back at something. - $noClient = Join-Path $script:CaseDir 'no-such-client' - $env:MAPBOX_CLAUDE_CLI = $noClient - $env:MAPBOX_CODEX_CLI = $noClient - $env:MAPBOX_CODE_CLI = $noClient - $env:MAPBOX_CURSOR_CLI = $noClient # What install.ps1 reads to decide where it is running. Set explicitly so # the same case means the same thing on Windows and on the machine this is # written on. @@ -570,22 +562,6 @@ function New-CaseEnv([string]$Name) { Clear-Env 'PROCESSOR_ARCHITEW6432' } -# Makes Claude Code and VS Code findable by install.ps1's lookup, through the -# override variables src/mcp.rs also reads. Neither is ever run. -function Set-McpClientsOnPath { - foreach ($pair in @(@('MAPBOX_CLAUDE_CLI', 'claude'), @('MAPBOX_CODE_CLI', 'code'))) { - if ($OnWindows) { - $file = Join-Path $script:CaseDir "$($pair[1]).cmd" - Set-Content -LiteralPath $file -Encoding ASCII -Value '@exit /b 0' - } else { - $file = Join-Path $script:CaseDir $pair[1] - Set-Content -LiteralPath $file -Value "#!/bin/sh`nexit 0" - & chmod 755 $file - } - Set-Item -Path "env:$($pair[0])" -Value $file - } -} - # Unset, never `$env:X = ''`. Windows PowerShell deletes a variable assigned # an empty string; PowerShell 7 keeps it, with an empty value - and an empty # PROCESSOR_ARCHITEW6432 is worse than none, because Windows' WoW64 handling @@ -1091,18 +1067,16 @@ try { Start-Case 'MCP, no console: not asked, not added' New-CaseEnv 'mcp-no-console' $env:MAPBOX_CLI_BASE_URL = "$($open.BaseUrl)/agent-channel" - Set-McpClientsOnPath Invoke-Installer Expect-Status 0 'exits 0' Expect-NoOut 'Add the Mapbox MCP servers' 'does not ask' - Expect-Out 'Skipped the Mapbox MCP servers for Claude Code, VS Code.' 'names each client once' + Expect-Out 'Skipped the Mapbox MCP servers.' 'says so' Expect-Equal 'False' "$(Test-Path -LiteralPath $env:MAPBOX_TEST_MCP_RAN)" 'and never runs mapbox mcp, which is the slow part' Expect-Out 'mapbox mcp install --global' 'gives the manual command' Start-Case 'MAPBOX_CLI_NO_AGENT_SETUP skips the MCP question too' New-CaseEnv 'mcp-opt-out' $env:MAPBOX_CLI_BASE_URL = "$($open.BaseUrl)/agent-channel" - Set-McpClientsOnPath $env:MAPBOX_CLI_NO_AGENT_SETUP = '1' Invoke-Installer Expect-Status 0 'exits 0' @@ -1110,12 +1084,11 @@ try { # printed in the PATH note above, is named mcp-opt-out. Expect-NoOut 'Mapbox MCP servers' 'says nothing about it' - Start-Case 'MCP, no client CLI on PATH: nothing said' - New-CaseEnv 'mcp-no-client' - $env:MAPBOX_CLI_BASE_URL = "$($open.BaseUrl)/agent-channel" + Start-Case 'MCP, no coding agent found: nothing said' + New-CaseEnv 'mcp-no-agent' Invoke-Installer Expect-Status 0 'exits 0' - Expect-NoOut 'Mapbox MCP servers' 'says nothing when there is nothing to add to' + Expect-NoOut 'Mapbox MCP servers' 'says nothing when there is nothing to add them to' } finally { if ($open) { Stop-ChannelServer $open } if ($gated) { Stop-ChannelServer $gated } diff --git a/scripts/test-install.sh b/scripts/test-install.sh index fbec674..9f7a0b4 100755 --- a/scripts/test-install.sh +++ b/scripts/test-install.sh @@ -283,9 +283,8 @@ make_channel "${CHANNEL}/v0.1.0-dev.abc1234" v0.1.0-dev.abc1234 # environment variables: MAPBOX_TEST_INSTALL_MODE (ok | already_installed) # and MAPBOX_TEST_UPDATE_MODE (clean | dirty, read only once install # reports already_installed). Defaults match the plain first-install case. -# MAPBOX_TEST_MCP_MODE (none | fresh | registered | fail) drives `mcp`. -# Whether the installer asks about MCP at all depends on which client CLIs -# are on PATH, not on this binary; see mcp_clients_on_path. +# MAPBOX_TEST_MCP_MODE (none | fresh | registered | fail) drives `mcp`; +# none is a machine with no client mapbox can register a server with. AGENT_CHANNEL="${ROOT}/agent-channel" mkdir -p "${AGENT_CHANNEL}/latest/build" cat >"${AGENT_CHANNEL}/latest/build/mapbox" <<'EOF' @@ -666,27 +665,9 @@ new_case_env() { # case-name # turn every marker case into a failure that looks like the marker broke. unset DISABLE_TELEMETRY MAPBOX_CLI_NO_TELEMETRY unset MAPBOX_CLI_NO_AGENT_SETUP MAPBOX_TEST_INSTALL_MODE MAPBOX_TEST_UPDATE_MODE MAPBOX_TEST_MCP_MODE - # The installer looks MCP clients up on PATH, and SAFE_PATH can hold a real - # one (Homebrew's codex), so each points at nothing unless a case says so. - MAPBOX_CLAUDE_CLI="${CASE_DIR}/no-such-client" - MAPBOX_CODEX_CLI="${CASE_DIR}/no-such-client" - MAPBOX_CODE_CLI="${CASE_DIR}/no-such-client" - MAPBOX_CURSOR_CLI="${CASE_DIR}/no-such-client" - export MAPBOX_CLAUDE_CLI MAPBOX_CODEX_CLI MAPBOX_CODE_CLI MAPBOX_CURSOR_CLI export MAPBOX_CLI_BASE_URL MAPBOX_INSTALL_DIR PATH } -# Puts Claude Code and VS Code on PATH, as far as the installer can tell. The -# fake mapbox answers `mcp` for them; these are never run. -mcp_clients_on_path() { - for c in claude code; do - printf '#!/bin/sh\nexit 0\n' >"${CASE_SHIMS}/${c}" - chmod 755 "${CASE_SHIMS}/${c}" - done - MAPBOX_CLAUDE_CLI=claude - MAPBOX_CODE_CLI=code -} - # Whether the installer ran `mapbox mcp` at all: the fake marks it in HOME. mcp_ran() { if [ -f "${HOME}/.mcp-ran" ]; then echo yes; else echo no; fi; } @@ -1337,6 +1318,7 @@ export MAPBOX_INSTALL_TILESETS=no run_piped && status=0 || status=$? expect_status 0 "$status" 'exits 0' expect_no_out 'coding agent' 'says nothing about a step that has nothing to do' +expect_no_out 'MCP' 'nor about the MCP servers' start 'MAPBOX_CLI_NO_AGENT_SETUP opts out even though an agent was detected' new_case_env agent-opt-out @@ -1344,7 +1326,6 @@ export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_CLI_NO_AGENT_SETUP=1 export MAPBOX_TEST_MCP_MODE=fresh -mcp_clients_on_path run_piped && status=0 || status=$? expect_status 0 "$status" 'exits 0' expect_no_out 'coding agent' 'skips the question entirely' @@ -1365,7 +1346,7 @@ start 'a terminal, answered no' new_case_env agent-tty-no export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no -run_interactive n && status=0 || status=$? +run_interactive "$(printf 'n\nn')" && status=0 || status=$? expect_status 0 "$status" 'a declined setup is not an install failure' expect_out 'Coding agents found: claude.' 'names the agent' expect_out 'Set up the mapbox CLI skill' 'asks' @@ -1381,7 +1362,7 @@ new_case_env agent-tty-yes-fresh export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_INSTALL_MODE=ok -run_interactive y && status=0 || status=$? +run_interactive "$(printf 'y\nn')" && status=0 || status=$? expect_status 0 "$status" 'exits 0' expect_out 'Wrote the mapbox CLI skill for: claude.' 'reports the skill written' expect_out 'Installed the Mapbox Agent Skills library for: claude.' 'reports the library installed' @@ -1392,7 +1373,7 @@ export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_INSTALL_MODE=already_installed export MAPBOX_TEST_UPDATE_MODE=clean -run_interactive y && status=0 || status=$? +run_interactive "$(printf 'y\nn')" && status=0 || status=$? expect_status 0 "$status" 'exits 0' expect_out 'Updated the Mapbox Agent Skills library for: claude.' 'updates in place' expect_no_out 'have local changes and were left alone' 'nothing was skipped' @@ -1403,7 +1384,7 @@ export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_INSTALL_MODE=already_installed export MAPBOX_TEST_UPDATE_MODE=dirty -run_interactive y && status=0 || status=$? +run_interactive "$(printf 'y\nn')" && status=0 || status=$? expect_status 0 "$status" 'exits 0' expect_out 'Some Mapbox Agent Skills have local changes and were left alone.' \ 'reports the local edit rather than discarding it' @@ -1421,12 +1402,11 @@ new_case_env mcp-no-tty export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_MCP_MODE=fresh -mcp_clients_on_path run_piped && status=0 || status=$? expect_status 0 "$status" 'exits 0 rather than blocking (124 would be a hang)' expect_no_out 'Add the Mapbox MCP servers' 'does not ask' expect_says "$(mcp_ran)" no 'and never runs mapbox mcp, which is the slow part' -expect_out 'Skipped the Mapbox MCP servers for Claude Code, VS Code.' 'names each client once' +expect_out 'Skipped the Mapbox MCP servers.' 'says so' expect_says "$(grep -c '^ - Skipped the Mapbox MCP servers' "$OUT")" 1 'top level when no question was asked' expect_out 'mapbox mcp install --global' 'gives the manual command' @@ -1435,10 +1415,9 @@ new_case_env mcp-tty-yes export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_MCP_MODE=fresh -mcp_clients_on_path run_interactive "$(printf 'n\ny')" && status=0 || status=$? expect_status 0 "$status" 'exits 0' -expect_out 'Add the Mapbox MCP servers to Claude Code, VS Code?' 'asks, separately from the skill' +expect_out 'Add the Mapbox MCP servers to your coding agents?' 'asks, separately from the skill' expect_out 'This runs mapbox mcp install --global' 'names the command it runs' expect_out 'This runs mapbox mcp install --global, now or any time later.' 'says it can be run later instead' expect_out 'Add them now? [y/N]' 'asks about now' @@ -1479,65 +1458,39 @@ new_case_env mcp-tty-no export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_MCP_MODE=fresh -mcp_clients_on_path run_interactive "$(printf 'n\nn')" && status=0 || status=$? expect_status 0 "$status" 'a declined setup is not an install failure' -expect_out 'Skipped the Mapbox MCP servers for Claude Code, VS Code.' 'says so' +expect_out 'Skipped the Mapbox MCP servers.' 'says so' expect_no_out 'Added the Mapbox MCP servers' 'adds nothing' expect_says "$(mcp_ran)" no 'and never runs mapbox mcp' -start 'MCP, no client CLI on PATH: not asked, nothing said' -new_case_env mcp-no-client -export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" -export MAPBOX_INSTALL_TILESETS=no -export MAPBOX_TEST_MCP_MODE=fresh -run_interactive n && status=0 || status=$? -expect_status 0 "$status" 'exits 0' -expect_no_out 'MCP' 'says nothing when there is nothing to add to' -expect_says "$(mcp_ran)" no 'and never runs mapbox mcp' - -start 'MCP, a client named by its override variable counts' -new_case_env mcp-override -export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" -export MAPBOX_INSTALL_TILESETS=no -export MAPBOX_TEST_MCP_MODE=fresh -printf '#!/bin/sh\nexit 0\n' >"${CASE_DIR}/my-cursor" -chmod 755 "${CASE_DIR}/my-cursor" -MAPBOX_CURSOR_CLI="${CASE_DIR}/my-cursor" -run_interactive "$(printf 'n\nn')" && status=0 || status=$? -expect_status 0 "$status" 'exits 0' -expect_out 'Skipped the Mapbox MCP servers for Cursor.' 'finds it where the variable points, as src/mcp.rs does' - start 'MCP, everything already registered: asked, and a yes says so' new_case_env mcp-registered export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_MCP_MODE=registered -mcp_clients_on_path run_interactive "$(printf 'n\ny')" && status=0 || status=$? expect_status 0 "$status" 'exits 0' -expect_out 'Add the Mapbox MCP servers to Claude Code, VS Code?' 'asks: only a yes runs the check' +expect_out 'Add the Mapbox MCP servers to your coding agents?' 'asks: only a yes runs the check' expect_out 'Mapbox MCP was already in Claude Code, VS Code' 'reports what was already there' expect_no_out 'Added Mapbox' 'claims to add nothing' -start 'MCP, a client on PATH that mapbox cannot run: a yes reports the failure' -new_case_env mcp-not-detected +start 'MCP, an agent found but no client mapbox can register with: a yes says so' +new_case_env mcp-no-client export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_MCP_MODE=none -mcp_clients_on_path run_interactive "$(printf 'n\ny')" && status=0 || status=$? -expect_status 0 "$status" 'mapbox itself is still installed' -expect_out 'Could not add the Mapbox MCP servers.' 'says nothing was added' -expect_no_out 'Not every Mapbox MCP server' 'rather than implying some were' -expect_out "Run 'mapbox mcp install --global' to see why and try again." 'points at the command' +expect_status 0 "$status" 'exits 0' +expect_out 'No Claude Code, Codex, VS Code or Cursor here to add them to.' 'says there was nothing to add them to' +expect_no_out 'Could not add' 'not as a failure' +expect_no_out 'to see why and try again' 'with nothing to retry' start 'MCP, a failed registration is reported, not fatal' new_case_env mcp-fail export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_MCP_MODE=fail -mcp_clients_on_path run_interactive "$(printf 'n\ny')" && status=0 || status=$? expect_status 0 "$status" 'mapbox itself is still installed' expect_out 'Could not add Mapbox MCP to VS Code' 'reports which one failed' @@ -1550,7 +1503,6 @@ new_case_env mcp-dumb export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" export MAPBOX_INSTALL_TILESETS=no export MAPBOX_TEST_MCP_MODE=fresh -mcp_clients_on_path TERM=dumb run_interactive "$(printf 'n\ny')" && status=0 || status=$? expect_status 0 "$status" 'exits 0' expect_out 'Added Mapbox MCP to Claude Code' 'still adds them' diff --git a/src/mcp.rs b/src/mcp.rs index ab05b50..5b22d92 100644 --- a/src/mcp.rs +++ b/src/mcp.rs @@ -899,61 +899,6 @@ mod tests { assert_eq!(flags.len(), CLIENTS.len()); } - /// The installers decide whether to offer `mcp install` by looking each - /// client up on PATH themselves, from a copy of this table, so a client - /// added here and not there would never be offered. - #[test] - fn installers_know_every_mcp_client() { - let read = |path: &str| { - std::fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join(path)) - .unwrap_or_else(|e| panic!("{path}: {e}")) - }; - // Only the lookup itself, so a row left in a comment or a matching - // string elsewhere in the script can't stand in for it. - let between = |text: &str, path: &str, start: &str, end: &str| -> String { - let from = text - .find(start) - .unwrap_or_else(|| panic!("{path} has no {start}")); - let len = text[from..] - .find(end) - .unwrap_or_else(|| panic!("{path} has no {end}")); - text[from..from + len].to_string() - }; - let sh = between( - &read("scripts/install.sh"), - "install.sh", - "mcp_clients_present() {", - "\n}\n", - ); - let ps1 = between( - &read("scripts/install.ps1"), - "install.ps1", - "$clientLabels = @{", - "$mcpClients = $mcpFound -join", - ); - for c in CLIENTS { - let row = format!("'{}|{}|{}'", c.binary_env, c.default_binary, c.label); - assert!(sh.contains(&row), "install.sh has no {row}"); - let row = format!( - "Id = '{}'; Var = '{}'; Bin = '{}'", - c.flag, c.binary_env, c.default_binary - ); - assert!(ps1.contains(&row), "install.ps1 has no {row}"); - let label = format!("'{}' = '{}'", c.flag, c.label); - assert!(ps1.contains(&label), "install.ps1 has no {label}"); - } - assert_eq!( - sh.matches("_CLI|").count(), - CLIENTS.len(), - "install.sh lists a client src/mcp.rs doesn't have" - ); - assert_eq!( - ps1.matches("; Var = '").count(), - CLIENTS.len(), - "install.ps1 lists a client src/mcp.rs doesn't have" - ); - } - #[test] fn every_verb_client_has_an_add_arm() { for client in CLIENTS.iter().filter(|c| c.kind == ClientKind::Verb) { From cd49e87eb1eb532aae488bcffe3a360093e1fc7a Mon Sep 17 00:00:00 2001 From: Mofei Zhu Date: Thu, 22 Oct 2026 15:20:20 +0300 Subject: [PATCH 4/6] Still offer the MCP servers on a machine with only VS Code VS Code takes an MCP server but no skill, so gating on the skill step's agent check dropped it. ~/.vscode now counts too, under the same opt-out. The not-found check tolerates spacing in the error JSON, and the PowerShell no-agent case asserts that mapbox mcp never ran. --- CHANGELOG.md | 2 +- README.md | 3 ++- scripts/install.ps1 | 10 ++++++---- scripts/install.sh | 8 +++++--- scripts/test-install.ps1 | 1 + scripts/test-install.sh | 20 ++++++++++++++++++++ 6 files changed, 35 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df9c6b1..709f396 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ that may never merge. They are not releases and are not listed here. - `install.sh`/`install.ps1`: the MCP question no longer waits several seconds before it appears. The installer asks whenever it found a coding - agent, and only after a yes does it look for Claude Code, Codex, VS Code + agent or VS Code, and only after a yes does it look for Claude Code, Codex, VS Code and Cursor and check what each already has. A reinstall therefore asks again even when every server is registered; a yes then reports each one as already there. diff --git a/README.md b/README.md index a73a59a..a1db135 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,8 @@ It then asks, separately, whether to add the [Mapbox MCP servers](#mcp-servers) to your coding agents, the same thing `mapbox mcp install --global` does: a yes adds them to whichever of Claude Code, Codex, VS Code and Cursor are installed, and skips any that already have them. It asks whenever it found a -coding agent above, with the same rules: no terminal means no. +coding agent above or VS Code is installed, with the same rules: no terminal +means no. Set `MAPBOX_CLI_NO_AGENT_SETUP=1` to skip both questions. On macOS and Linux, `install.sh` also offers to install the diff --git a/scripts/install.ps1 b/scripts/install.ps1 index de126fe..2399c51 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -841,8 +841,10 @@ Windows 11 on Arm has and Windows 10 on Arm does not. # A separate question from the skill above: this registers a hosted # server in the coding agent's own config, next to servers the user # added, rather than writing a directory this CLI owns. Asked only - # when the skill step above found a coding agent, with the same - # no-console rule. + # when the skill step above found a coding agent, or VS Code is + # there, with the same opt-out and no-console rule. VS Code takes an + # MCP server but no skill, so the skill step can't find it; + # ~/.vscode is where it keeps extensions on every OS. # # Nothing here checks which clients are installed or what they # already have, for the reason install.sh gives: that check runs each @@ -850,7 +852,7 @@ Windows 11 on Arm has and Windows 10 on Arm does not. # mcp install` tries each client itself once someone says yes, so a # reinstall asks again, a yes reports what was already there, and a # no is reported as skipped. - if ($agentsFound) { + if ($AgentSetupAllowed -and ($agentsFound -or (Test-Path -LiteralPath (Join-Path $HOME '.vscode') -PathType Container))) { $previousErrorAction = $ErrorActionPreference $ErrorActionPreference = 'Continue' try { @@ -914,7 +916,7 @@ Windows 11 on Arm has and Windows 10 on Arm does not. } } if (-not $mcpOk) { - if ($mcpErr -like '*"code":"mcp_client_not_found"*') { + if ($mcpErr -match '"code"\s*:\s*"mcp_client_not_found"') { # The agent found above takes skills but not # an MCP server this CLI can register. Write-Skip 'No Claude Code, Codex, VS Code or Cursor here to add them to.' diff --git a/scripts/install.sh b/scripts/install.sh index 4f1f114..94766f9 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1013,7 +1013,9 @@ fi # A separate question from the skill above: this registers a hosted server in # the coding agent's own config, next to servers the user added, rather than # writing a directory this CLI owns. Asked only when the skill step above -# found a coding agent, with the same opt-out and no-terminal rule. +# found a coding agent, or VS Code is there, with the same opt-out and +# no-terminal rule. VS Code takes an MCP server but no skill, so the skill +# step can't find it; ~/.vscode is where it keeps extensions on every OS. # # Nothing here checks which clients are installed or what they already have. # That check runs each client's own CLI, and Claude Code's takes over a second @@ -1055,7 +1057,7 @@ mcp_grouped() { # mcp_grouped } END { for (i = 1; i <= n; i++) print order[i] "|" who[order[i]] }' } -if [ "$agents_found" = yes ]; then +if agent_setup_allowed && { [ "$agents_found" = yes ] || [ -d "${HOME}/.vscode" ]; }; then do_mcp_setup=no if have_tty; then ask_title "Add the Mapbox MCP servers to your coding agents?" @@ -1092,7 +1094,7 @@ if [ "$agents_found" = yes ]; then esac done <"${WORK_DIR}/mcp-rows" if [ "$mcp_ok" = no ]; then - if grep -qs '"code":"mcp_client_not_found"' "${WORK_DIR}/mcp-install.log" "${WORK_DIR}/mcp-install.json"; then + if grep -Eqs '"code"[[:space:]]*:[[:space:]]*"mcp_client_not_found"' "${WORK_DIR}/mcp-install.log" "${WORK_DIR}/mcp-install.json"; then # The agent found above takes skills but not an MCP # server this CLI can register (Goose, Amp, ...). step_skip "No Claude Code, Codex, VS Code or Cursor here to add them to." diff --git a/scripts/test-install.ps1 b/scripts/test-install.ps1 index 9231515..bb76d68 100644 --- a/scripts/test-install.ps1 +++ b/scripts/test-install.ps1 @@ -1089,6 +1089,7 @@ try { Invoke-Installer Expect-Status 0 'exits 0' Expect-NoOut 'Mapbox MCP servers' 'says nothing when there is nothing to add them to' + Expect-Equal 'False' "$(Test-Path -LiteralPath $env:MAPBOX_TEST_MCP_RAN)" 'and never runs mapbox mcp' } finally { if ($open) { Stop-ChannelServer $open } if ($gated) { Stop-ChannelServer $gated } diff --git a/scripts/test-install.sh b/scripts/test-install.sh index 9f7a0b4..f3a5c2e 100755 --- a/scripts/test-install.sh +++ b/scripts/test-install.sh @@ -226,6 +226,7 @@ case "\${1:-}" in exit 1 ;; mcp) + : >"\${HOME}/.mcp-ran" echo '{"code":"mcp_client_not_found"}' >&2 exit 1 ;; @@ -1475,6 +1476,25 @@ expect_out 'Add the Mapbox MCP servers to your coding agents?' 'asks: only a yes expect_out 'Mapbox MCP was already in Claude Code, VS Code' 'reports what was already there' expect_no_out 'Added Mapbox' 'claims to add nothing' +start 'MCP, only VS Code, which takes no skill: still offered' +new_case_env mcp-vscode-only +export MAPBOX_INSTALL_TILESETS=no +mkdir -p "${HOME}/.vscode" +run_piped && status=0 || status=$? +expect_status 0 "$status" 'exits 0' +expect_no_out 'Coding agents found' 'the skill step found nothing' +expect_out 'Skipped the Mapbox MCP servers.' 'but the MCP step still runs' +expect_says "$(mcp_ran)" no 'without running mapbox mcp' + +start 'MCP, only VS Code, opted out: nothing said' +new_case_env mcp-vscode-opt-out +export MAPBOX_INSTALL_TILESETS=no +export MAPBOX_CLI_NO_AGENT_SETUP=1 +mkdir -p "${HOME}/.vscode" +run_piped && status=0 || status=$? +expect_status 0 "$status" 'exits 0' +expect_no_out 'MCP' 'the opt-out covers it too' + start 'MCP, an agent found but no client mapbox can register with: a yes says so' new_case_env mcp-no-client export MAPBOX_CLI_BASE_URL="file://${AGENT_CHANNEL}" From b6f5f3e33812afe9ec5cf159164db3235757a119 Mon Sep 17 00:00:00 2001 From: Mofei Zhu Date: Thu, 22 Oct 2026 18:21:36 +0300 Subject: [PATCH 5/6] Prepare 1.1.1: bump the version and add its changelog heading --- CHANGELOG.md | 2 ++ Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 8 ++++---- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 709f396..b96f30f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ that may never merge. They are not releases and are not listed here. ## Unreleased +## 1.1.1 - 2026-10-22 + - `install.sh`/`install.ps1`: the MCP question no longer waits several seconds before it appears. The installer asks whenever it found a coding agent or VS Code, and only after a yes does it look for Claude Code, Codex, VS Code diff --git a/Cargo.lock b/Cargo.lock index e977738..c19a4a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -816,7 +816,7 @@ checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" [[package]] name = "mapbox-cli" -version = "1.1.0" +version = "1.1.1" dependencies = [ "anyhow", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index e32b781..0654710 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mapbox-cli" -version = "1.1.0" +version = "1.1.1" edition = "2021" description = "A command-line interface for Mapbox APIs, with commands generated at build time from OpenAPI specs." repository = "https://github.com/mapbox/mapbox-cli" diff --git a/README.md b/README.md index a1db135..0bd6d97 100644 --- a/README.md +++ b/README.md @@ -82,11 +82,11 @@ taking the newest, with or without the leading `v`, so the version `mapbox --version` prints can be pasted straight in: ```sh -curl -fsSL https://cli.mapbox.com/install.sh | MAPBOX_CLI_VERSION=1.1.0 sh +curl -fsSL https://cli.mapbox.com/install.sh | MAPBOX_CLI_VERSION=1.1.1 sh ``` ```powershell -$env:MAPBOX_CLI_VERSION = '1.1.0'; irm https://cli.mapbox.com/install.ps1 | iex +$env:MAPBOX_CLI_VERSION = '1.1.1'; irm https://cli.mapbox.com/install.ps1 | iex ``` Both scripts put the install directory on your `PATH`: `install.sh` adds a @@ -143,7 +143,7 @@ Six targets are published: `aarch64-apple-darwin`, `x86_64-apple-darwin`, then extract: ```sh -version=v1.1.0 +version=v1.1.1 file=mapbox-${version}-aarch64-apple-darwin.tar.gz curl -fsSLO "https://cli.mapbox.com/${version}/${file}" @@ -158,7 +158,7 @@ On Windows the archive is a `.zip`, and PowerShell can read the manifest directly: ```powershell -$version = 'v1.1.0' +$version = 'v1.1.1' $target = 'x86_64-pc-windows-msvc' $manifest = Invoke-RestMethod "https://cli.mapbox.com/$version/manifest.json" From 03690710d63872b73caba63f384d246862e5efaa Mon Sep 17 00:00:00 2001 From: Mofei Zhu Date: Thu, 22 Oct 2026 18:23:13 +0300 Subject: [PATCH 6/6] Note the update check in the 1.1.1 changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b96f30f..4a0cf2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,12 @@ that may never merge. They are not releases and are not listed here. ## 1.1.1 - 2026-10-22 +- The update check now runs in published builds. 0.3.0 through 1.1.0 were + built with it off, so they never said a newer version existed. At a + terminal, at most once a day, `mapbox` now prints a notice on stderr when + one does; `MAPBOX_NO_UPDATE_CHECK=1` or + `mapbox config set update-check off` turns it off. Nothing reaches stdout, + and piped or CI runs never check. - `install.sh`/`install.ps1`: the MCP question no longer waits several seconds before it appears. The installer asks whenever it found a coding agent or VS Code, and only after a yes does it look for Claude Code, Codex, VS Code