Skip to content

fix: resolve MCP binary paths to full absolute paths for PATH-independent startup#179

Merged
marcusquinn merged 2 commits intomainfrom
fix/opencode-mcp-startup
Jan 24, 2026
Merged

fix: resolve MCP binary paths to full absolute paths for PATH-independent startup#179
marcusquinn merged 2 commits intomainfrom
fix/opencode-mcp-startup

Conversation

@marcusquinn
Copy link
Copy Markdown
Owner

@marcusquinn marcusquinn commented Jan 24, 2026

Summary

  • Fixes MCP servers failing to start in OpenCode because bare binary names (e.g., repomix, chrome-devtools-mcp) can't be found when ~/.bun/bin isn't in the spawned process's PATH
  • Adds resolve_mcp_binary_path() helper that checks ~/.bun/bin, /opt/homebrew/bin, /usr/local/bin, ~/.local/bin, and ~/.npm-global/bin
  • Adds update_mcp_paths_in_opencode() to rewrite all MCP commands in opencode.json to use full absolute paths

Root Cause

The v2.79 migration (perf: install MCP packages globally for instant startup) correctly installed packages globally via bun install -g, but the config migration wrote bare binary names. OpenCode uses StdioClientTransport which spawns processes with process.env - when launched from a non-shell context (desktop app, launchd, spotlight), ~/.bun/bin isn't in PATH.

Changes

Function Purpose
resolve_mcp_binary_path() Resolves binary name to full path across common install locations
update_mcp_paths_in_opencode() Rewrites all local MCP commands to full paths, handles stale paths
cleanup_deprecated_mcps() Updated to use full paths during npx→binary migration
install_mcp_packages() Now calls path resolution after installing packages

Testing

Verified all 12 local MCPs resolve correctly:

  • ~/.bun/bin/ for bun-installed packages (chrome-devtools-mcp, repomix, playwriter, etc.)
  • /opt/homebrew/bin/ for brew-installed packages (osgrep, auggie, mcp-local-wp)
  • ~/.local/bin/ for pipx/uv-installed packages (analytics-mcp, outscraper-mcp-server)
  • /opt/homebrew/bin/node for node-script MCPs (quickfile, amazon-order-history)

Summary by CodeRabbit

  • Chores
    • Improved MCP tool integration with automatic binary path resolution and configuration normalization.
    • Ensures full binary paths are recorded and propagated to configuration to make tool startup PATH-independent.
    • Expanded installation handling for node-based MCP packages so their paths are resolved and updated after install.

✏️ Tip: You can customize this high-level summary in your review settings.

…dent startup

OpenCode spawns MCP processes without inheriting shell profile PATH
(~/.bun/bin not available). The previous migration from npx to bare
binary names broke MCPs when launched from non-shell contexts.

- Add resolve_mcp_binary_path() to check ~/.bun/bin, /opt/homebrew/bin,
  /usr/local/bin, ~/.local/bin, and ~/.npm-global/bin
- Add update_mcp_paths_in_opencode() to rewrite all MCP commands to
  full paths in opencode.json
- Update cleanup_deprecated_mcps migration to use full paths
- Also resolve 'node' binary for node-script MCPs (quickfile, etc.)
- Run path resolution both during MCP install and deprecated cleanup
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @marcusquinn, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical issue preventing MCP servers from starting reliably in OpenCode, particularly in non-shell environments where the system's PATH variable might not include necessary binary locations. By implementing robust binary path resolution and persisting these absolute paths directly into the opencode.json configuration, the changes ensure that all local MCP commands can be executed consistently, regardless of the environment's PATH settings. This significantly improves the stability and startup reliability of MCP services.

Highlights

  • MCP Server Startup Fix: Resolves issues where MCP (Micro-Container Platform) servers failed to start in OpenCode due to bare binary names not being found when ~/.bun/bin was not in the spawned process's PATH.
  • Absolute Path Resolution: Introduces a new helper function, resolve_mcp_binary_path(), which intelligently searches common installation directories (e.g., ~/.bun/bin, /opt/homebrew/bin, /usr/local/bin) to find the absolute path of an MCP binary.
  • opencode.json Path Rewriting: Adds update_mcp_paths_in_opencode() to rewrite all local MCP commands within opencode.json to use these newly resolved absolute paths, ensuring PATH-independent startup.
  • Integration with Setup Process: The cleanup_deprecated_mcps() and install_mcp_packages() functions have been updated to utilize the new path resolution logic, ensuring that MCP configurations are correctly migrated and installed with absolute paths.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 24, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds two helpers—resolve_mcp_binary_path() and update_mcp_paths_in_opencode()—to resolve full MCP binary paths from likely install locations or PATH and to update opencode.json MCP command entries to use those full paths; migrates existing MCP usage in the setup flow to the resolved paths.

Changes

Cohort / File(s) Summary
MCP Binary Path Helpers
setup.sh
Added resolve_mcp_binary_path() to probe common install locations and fallback to which/PATH. Added update_mcp_paths_in_opencode() to scan opencode.json and replace MCP command[0] entries with resolved full binary paths, including handling for node-based commands and stale paths.
MCP Command Path Refactoring
setup.sh
Refactored code (e.g., cleanup_deprecated_mcps, outscraper migration and MCP install loops) to use resolved full binary paths instead of bare names; preserved existing flags/args and added post-install propagation of full paths into opencode.json.
Path Normalization & Mapping
setup.sh
Introduced associative mapping for Node.js MCP packages and adjusted iteration to use keys; added bulk path normalization after MCP updates so startup is PATH-independent.
Messages & Outputs
setup.sh
Updated informational messages to reference resolved full binary paths and to emit success when opencode.json entries are updated.

Sequence Diagram(s)

sequenceDiagram
  participant Setup as Setup Script
  participant FS as Filesystem
  participant PATH as System PATH
  participant OC as opencode.json

  Setup->>FS: Check common install locations for <binary>
  alt found in common location
    FS-->>Setup: Return full path
  else not found
    Setup->>PATH: fallback `which` lookup
    PATH-->>Setup: Return full path or empty
  end
  Setup->>OC: Read MCP entries
  Setup->>Setup: Replace command[0] with resolved full path
  Setup->>OC: Write updated opencode.json (if changed)
  OC-->>Setup: Acknowledge update
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🧭 Binaries once lost in PATH's roam,
Helpers now guide each MCP home,
opencode.json updated, neat and bright,
Full paths marching into the light ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: resolving MCP binary paths to full absolute paths for PATH-independent startup, which is the core objective of all modifications.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 416 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Sat Jan 24 19:21:20 UTC 2026: Code review monitoring started
Sat Jan 24 19:21:20 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 416
Sat Jan 24 19:21:20 UTC 2026: Qlty - 0 issues found, auto-formatting applied
Sat Jan 24 19:21:22 UTC 2026: Codacy analysis completed with auto-fixes

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 416
  • VULNERABILITIES: 0

Generated on: Sat Jan 24 19:22:42 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request is a great improvement for making MCP startups more robust by resolving binary paths to absolute paths. The new helper functions resolve_mcp_binary_path and update_mcp_paths_in_opencode are well-structured and handle many cases, including stale paths. I've found a couple of areas for improvement: one is a small refactoring to avoid a duplicate jq call and improve security in cleanup_deprecated_mcps, and the other is a potential bug in update_mcp_paths_in_opencode where some interpreters like python are skipped from path resolution, which seems to contradict the PR's goal. Overall, these are solid changes.

setup.sh Outdated

# Skip special commands (docker, node, etc.)
case "$current_cmd" in
docker|node|python|python3|bash|sh) continue ;;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The case statement skips resolving paths for python, python3, bash, and sh. This seems to contradict the goal of this pull request, which is to make MCP startup PATH-independent by using full absolute paths. If an MCP command uses a bare python3, it will not be resolved and might fail if python3 is not in the PATH of the spawned process. Since node is handled specially, these other interpreters could be removed from this case statement to be handled by the general path resolution logic.

Suggested change
docker|node|python|python3|bash|sh) continue ;;
docker|node) continue ;;

setup.sh Outdated
Comment on lines 246 to 264
if jq -e ".mcp | to_entries[] | select(.value.command != null) | select(.value.command | join(\" \") | test(\"npx.*${pkg}|bunx.*${pkg}|pipx.*run.*${pkg}\"))" "$tmp_config" > /dev/null 2>&1; then
if command -v "$bin_name" &> /dev/null; then
# Find the MCP key and update its command to bare binary
# Resolve full path for the binary
local full_path
full_path=$(resolve_mcp_binary_path "$bin_name")
if [[ -n "$full_path" ]]; then
# Find the MCP key and update its command to full path
local mcp_key
mcp_key=$(jq -r ".mcp | to_entries[] | select(.value.command != null) | select(.value.command | join(\" \") | test(\"npx.*${pkg}|bunx.*${pkg}|pipx.*run.*${pkg}\")) | .key" "$tmp_config" 2>/dev/null | head -1)
if [[ -n "$mcp_key" ]]; then
# Preserve --mcp flag for repomix
if [[ "$bin_name" == "repomix" ]]; then
jq ".mcp[\"$mcp_key\"].command = [\"$bin_name\", \"--mcp\"]" "$tmp_config" > "${tmp_config}.new" && mv "${tmp_config}.new" "$tmp_config"
jq --arg p "$full_path" ".mcp[\"$mcp_key\"].command = [\$p, \"--mcp\"]" "$tmp_config" > "${tmp_config}.new" && mv "${tmp_config}.new" "$tmp_config"
else
jq ".mcp[\"$mcp_key\"].command = [\"$bin_name\"]" "$tmp_config" > "${tmp_config}.new" && mv "${tmp_config}.new" "$tmp_config"
jq --arg p "$full_path" ".mcp[\"$mcp_key\"].command = [\$p]" "$tmp_config" > "${tmp_config}.new" && mv "${tmp_config}.new" "$tmp_config"
fi
((cleaned++))
fi
fi
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This block can be made more efficient and safer. It currently executes the same jq query twice to check for existence and then to get the key. Also, it uses shell variable interpolation ("$mcp_key") inside the jq script, which is not recommended. You can refactor this to run the query only once and use jq's --arg to pass the key safely.

Suggested change
if jq -e ".mcp | to_entries[] | select(.value.command != null) | select(.value.command | join(\" \") | test(\"npx.*${pkg}|bunx.*${pkg}|pipx.*run.*${pkg}\"))" "$tmp_config" > /dev/null 2>&1; then
if command -v "$bin_name" &> /dev/null; then
# Find the MCP key and update its command to bare binary
# Resolve full path for the binary
local full_path
full_path=$(resolve_mcp_binary_path "$bin_name")
if [[ -n "$full_path" ]]; then
# Find the MCP key and update its command to full path
local mcp_key
mcp_key=$(jq -r ".mcp | to_entries[] | select(.value.command != null) | select(.value.command | join(\" \") | test(\"npx.*${pkg}|bunx.*${pkg}|pipx.*run.*${pkg}\")) | .key" "$tmp_config" 2>/dev/null | head -1)
if [[ -n "$mcp_key" ]]; then
# Preserve --mcp flag for repomix
if [[ "$bin_name" == "repomix" ]]; then
jq ".mcp[\"$mcp_key\"].command = [\"$bin_name\", \"--mcp\"]" "$tmp_config" > "${tmp_config}.new" && mv "${tmp_config}.new" "$tmp_config"
jq --arg p "$full_path" ".mcp[\"$mcp_key\"].command = [\$p, \"--mcp\"]" "$tmp_config" > "${tmp_config}.new" && mv "${tmp_config}.new" "$tmp_config"
else
jq ".mcp[\"$mcp_key\"].command = [\"$bin_name\"]" "$tmp_config" > "${tmp_config}.new" && mv "${tmp_config}.new" "$tmp_config"
jq --arg p "$full_path" ".mcp[\"$mcp_key\"].command = [\$p]" "$tmp_config" > "${tmp_config}.new" && mv "${tmp_config}.new" "$tmp_config"
fi
((cleaned++))
fi
fi
fi
# Find MCP key using npx/bunx/pipx for this package
local mcp_key
mcp_key=$(jq -r ".mcp | to_entries[] | select(.value.command != null) | select(.value.command | join(\" \") | test(\"npx.*${pkg}|bunx.*${pkg}|pipx.*run.*${pkg}\")) | .key" "$tmp_config" 2>/dev/null | head -1)
if [[ -n "$mcp_key" ]]; then
# Resolve full path for the binary
local full_path
full_path=$(resolve_mcp_binary_path "$bin_name")
if [[ -n "$full_path" ]]; then
# Preserve --mcp flag for repomix
if [[ "$bin_name" == "repomix" ]]; then
jq --arg k "$mcp_key" --arg p "$full_path" '.mcp[$k].command = [$p, "--mcp"]' "$tmp_config" > "${tmp_config}.new" && mv "${tmp_config}.new" "$tmp_config"
else
jq --arg k "$mcp_key" --arg p "$full_path" '.mcp[$k].command = [$p]' "$tmp_config" > "${tmp_config}.new" && mv "${tmp_config}.new" "$tmp_config"
fi
((cleaned++))
fi
fi

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@setup.sh`:
- Around line 2219-2248: The resolve_mcp_binary_path function should use the
portable builtin command -v instead of which and explicitly return success;
replace the fallback assignment using which (inside resolve_mcp_binary_path)
with a command -v invocation (e.g., capture output of command -v "$bin_name"
2>/dev/null || true) and add an explicit "return 0" at the end of
resolve_mcp_binary_path so the function always returns success when finished.

…preters

- Replace 'which' with 'command -v' (POSIX-portable)
- Add explicit 'return 0' to resolve_mcp_binary_path
- Remove python/bash/sh from skip list so all interpreters get resolved
- Optimize cleanup_deprecated_mcps: single jq query instead of check+get
- Use --arg for all jq key interpolation (safer than string interpolation)
@sonarqubecloud
Copy link
Copy Markdown

@github-actions
Copy link
Copy Markdown
Contributor

🔍 Code Quality Report

�[0;35m[MONITOR]�[0m Code Review Monitoring Report

�[0;34m[INFO]�[0m Latest Quality Status:
SonarCloud: 0 bugs, 0 vulnerabilities, 416 code smells

�[0;34m[INFO]�[0m Recent monitoring activity:
Sat Jan 24 19:27:11 UTC 2026: Code review monitoring started
Sat Jan 24 19:27:12 UTC 2026: SonarCloud - Bugs: 0, Vulnerabilities: 0, Code Smells: 416
Sat Jan 24 19:27:12 UTC 2026: Qlty - 0 issues found, auto-formatting applied
Sat Jan 24 19:27:14 UTC 2026: Codacy analysis completed with auto-fixes

📈 Current Quality Metrics

  • BUGS: 0
  • CODE SMELLS: 416
  • VULNERABILITIES: 0

Generated on: Sat Jan 24 19:28:25 UTC 2026


Generated by AI DevOps Framework Code Review Monitoring

@marcusquinn marcusquinn merged commit bac002c into main Jan 24, 2026
9 checks passed
marcusquinn added a commit that referenced this pull request Jan 24, 2026
Bash associative arrays mishandle @ in keys (e.g. @steipete/claude-code-mcp)
during expansion. Using parallel indexed arrays and jq --arg for safe regex
matching avoids both the bash @ conflict and regex escaping issues.

Fixes syntax error at setup.sh:233 introduced in PR #179.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@marcusquinn marcusquinn deleted the fix/opencode-mcp-startup branch February 21, 2026 01:59
@marcusquinn marcusquinn added the code-reviews-actioned All review feedback has been actioned label Mar 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code-reviews-actioned All review feedback has been actioned

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant