Skip to content

fix(opencode): use local MCP config#1383

Open
rudironsoni wants to merge 5 commits into
headroomlabs-ai:mainfrom
rudironsoni:fix/opencode-mcp-local
Open

fix(opencode): use local MCP config#1383
rudironsoni wants to merge 5 commits into
headroomlabs-ai:mainfrom
rudironsoni:fix/opencode-mcp-local

Conversation

@rudironsoni

@rudironsoni rudironsoni commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes OpenCode Headroom MCP configuration across wrap, MCP install/status/uninstall, and persistent install docs/CLI.

OpenCode was being configured to use a remote HTTP MCP endpoint at /mcp, but the Headroom proxy does not expose MCP there. The correct OpenCode configuration is a local stdio MCP server that runs headroom mcp serve.

Closes #1380

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Tests

Changes Made

  • Changed OpenCode MCP registration to emit type: "local" with command: ["headroom", "mcp", "serve"].
  • Changed OpenCode MCP environment serialization from env to OpenCode's environment key, while still reading legacy env entries.
  • Removed generated remote /mcp entries from OpenCode wrap/runtime config.
  • Made wrap opencode --no-mcp skip persistent mcp.headroom injection.
  • Kept provider-only OpenCode config injection from writing MCP; MCP persistence is owned by the registrar path.
  • Made headroom mcp status and headroom mcp uninstall use the registrar lifecycle so OpenCode is covered.
  • Added opencode to persistent install --target choices.
  • Clarified OpenCode persistent install docs to use --scope provider for direct opencode.json edits.
  • Added regression coverage for registrar serialization, wrap behavior, runtime config, provider-scope install, MCP CLI lifecycle, and install target parsing.

Testing

  • rtk .venv/bin/python -m pytest tests/test_mcp_registry tests/test_cli/test_mcp.py tests/test_cli/test_wrap_opencode.py tests/test_providers_opencode_config.py tests/test_providers_opencode_install.py tests/test_install -q
  • Result after absorbing fix(opencode): write local MCP config #1381 overlap: 263 passed, 1 skipped
  • Targeted Ruff check passed for the changed Python/test files.
  • Targeted Ruff format check passed for the changed Python/test files.
  • Isolated HOME smoke tests with real opencode mcp list --pure.

Real Behavior Proof

  • headroom mcp install --agent opencode --proxy-url http://127.0.0.1:9000 --force against an isolated HOME wrote a valid local OpenCode MCP entry with environment.HEADROOM_PROXY_URL.
  • opencode mcp list --pure against that isolated HOME connected to headroom mcp serve.
  • headroom wrap opencode --prepare-only --no-rtk --no-serena --port 9001 wrote local MCP plus provider config.
  • headroom wrap opencode --prepare-only --no-rtk --no-serena --no-mcp --port 9002 wrote provider config without mcp.headroom.
  • Generated runtime OPENCODE_CONFIG_CONTENT was accepted by opencode mcp list --pure; include_mcp=False reported no MCP servers.
  • headroom mcp status detected the isolated OpenCode config and read the custom proxy URL.
  • headroom mcp uninstall removed mcp.headroom from the isolated OpenCode config while leaving provider config intact.

Review Readiness

  • I have performed a self-review
  • This PR is ready for human review

@JerrettDavis JerrettDavis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the OpenCode local MCP and registrar-based mcp status/uninstall changes. I ran uv run --with pytest --with pytest-asyncio python -m pytest tests/test_mcp_registry_opencode.py tests/test_providers_opencode_config.py tests/test_cli/test_wrap_opencode.py tests/test_cli/test_mcp.py tests/test_cli/test_install_cli.py -q: 127 passed, 3 skipped; and ruff check on the changed Python/test files passed. No code-review blockers found.

Coordination note: this overlaps heavily with #1381, which I also reviewed. Please do not merge both independently without resolving the overlap; #1383 is the broader version because it includes the mcp status/uninstall registrar lifecycle work.

@rudironsoni

Copy link
Copy Markdown
Contributor Author

Reviewed the OpenCode local MCP and registrar-based mcp status/uninstall changes. I ran uv run --with pytest --with pytest-asyncio python -m pytest tests/test_mcp_registry_opencode.py tests/test_providers_opencode_config.py tests/test_cli/test_wrap_opencode.py tests/test_cli/test_mcp.py tests/test_cli/test_install_cli.py -q: 127 passed, 3 skipped; and ruff check on the changed Python/test files passed. No code-review blockers found.

Coordination note: this overlaps heavily with #1381, which I also reviewed. Please do not merge both independently without resolving the overlap; #1383 is the broader version because it includes the mcp status/uninstall registrar lifecycle work.

Which approach do you suggest us to follow?

@github-actions

github-actions Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

PR governance

This PR does not yet satisfy the required template fields:

  • Paste real command output or artifact links in TestingTest Output.
  • Fill in Real Behavior ProofEnvironment.
  • Fill in Real Behavior ProofExact command / steps.
  • Fill in Real Behavior ProofObserved result.
  • Fill in Real Behavior ProofNot tested.

Please update the PR body, or move the PR back to draft while it is still in progress.

@github-actions github-actions Bot added status: needs author action Pull request body or readiness checklist still needs author updates status: ci failing Required or reported CI checks are failing and removed status: ci failing Required or reported CI checks are failing labels Jun 24, 2026
@rudironsoni

Copy link
Copy Markdown
Contributor Author

Agreed. We should not merge #1381 and #1383 independently.

I kept #1383 as the merge target because it includes the broader registrar lifecycle work for headroom mcp status / headroom mcp uninstall. I also folded in the useful unique pieces from #1381:

  • changelog entry
  • clearer OpenCode docs for runtime config, plugin, and local MCP
  • provider-scope install coverage that confirms provider-only config does not add MCP
  • cleaner provider injection ownership, so inject_opencode_provider_config() no longer writes MCP at all

I also fixed the Ruff formatting failure reported by CI.

Verification after these updates:

  • rtk .venv/bin/python -m pytest tests/test_mcp_registry tests/test_cli/test_mcp.py tests/test_cli/test_wrap_opencode.py tests/test_providers_opencode_config.py tests/test_providers_opencode_install.py tests/test_install -q
  • Result: 263 passed, 1 skipped
  • rtk .venv/bin/ruff check ... on changed Python/test files
  • rtk .venv/bin/ruff format --check ... on changed Python/test files

@JerrettDavis JerrettDavis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-reviewed after the provider-injection follow-up. The new commit correctly keeps provider/runtime OpenCode config MCP-free and leaves MCP persistence to the registrar lifecycle, while preserving existing non-Headroom MCP entries.

Local validation run:

  • uv run --extra proxy --with pytest --with pytest-asyncio python -m pytest tests/test_mcp_registry_opencode.py tests/test_providers_opencode_config.py tests/test_cli/test_wrap_opencode.py tests/test_cli/test_mcp.py tests/test_cli/test_install_cli.py -q (133 passed)
  • uv run ruff check headroom/providers/opencode/config.py headroom/cli/wrap.py headroom/cli/install.py tests/test_mcp_registry_opencode.py tests/test_providers_opencode_config.py tests/test_cli/test_wrap_opencode.py tests/test_cli/test_mcp.py tests/test_cli/test_install_cli.py

CI is green. Approval stands.

@rudironsoni

Copy link
Copy Markdown
Contributor Author

Re-reviewed after the provider-injection follow-up. The new commit correctly keeps provider/runtime OpenCode config MCP-free and leaves MCP persistence to the registrar lifecycle, while preserving existing non-Headroom MCP entries.

Local validation run:

  • uv run --extra proxy --with pytest --with pytest-asyncio python -m pytest tests/test_mcp_registry_opencode.py tests/test_providers_opencode_config.py tests/test_cli/test_wrap_opencode.py tests/test_cli/test_mcp.py tests/test_cli/test_install_cli.py -q (133 passed)
  • uv run ruff check headroom/providers/opencode/config.py headroom/cli/wrap.py headroom/cli/install.py tests/test_mcp_registry_opencode.py tests/test_providers_opencode_config.py tests/test_cli/test_wrap_opencode.py tests/test_cli/test_mcp.py tests/test_cli/test_install_cli.py

CI is green. Approval stands.

@JerrettDavis can you merge?

@rudironsoni rudironsoni force-pushed the fix/opencode-mcp-local branch from ab43484 to 6fa6277 Compare June 25, 2026 09:11
@github-actions github-actions Bot added the status: has conflicts Pull request has merge conflicts with the base branch label Jun 25, 2026
@rudironsoni rudironsoni force-pushed the fix/opencode-mcp-local branch from ca147bc to e1b23f3 Compare June 25, 2026 16:12
@github-actions github-actions Bot removed the status: has conflicts Pull request has merge conflicts with the base branch label Jun 25, 2026
@rudironsoni rudironsoni force-pushed the fix/opencode-mcp-local branch 2 times, most recently from 842d46e to 61a9ccf Compare June 26, 2026 11:07

@JerrettDavis JerrettDavis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for keeping this updated. The OpenCode MCP behavior itself is moving in the right direction, and the CI is green, but the latest docs edit breaks two Markdown tables in docs/content/docs/opencode.mdx. Both the 'What wrap opencode Does' and 'Environment Variables' tables now have a header row followed immediately by data rows with no separator row, so they will render as plain text instead of tables. Please restore the separator rows (for example, |---|---|) before this is mergeable.\n\nGiven that #1381 is the narrower version of the same core OpenCode MCP fix, it would also be good to either coordinate which PR should land or split the broader mcp status/uninstall and Serena context changes into their own follow-up after the focused fix merges.

@rudironsoni rudironsoni force-pushed the fix/opencode-mcp-local branch 5 times, most recently from a0e766d to 3c7b967 Compare June 29, 2026 15:19

@JerrettDavis JerrettDavis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the latest update. This clears the prior concern from my side.

The uninstall/status path now delegates to the registrar abstraction instead of duplicating Claude-specific CLI/file behavior in the CLI command, and ClaudeRegistrar still handles both the CLI and legacy ~/.claude/mcp.json fallback. The OpenCode Serena context change is also covered and matches the local-agent use case.

Local check: python -m pytest tests/test_cli/test_mcp.py tests/test_cli/test_wrap_opencode.py -q passed (48 passed, 3 skipped). CI is green.

@rudironsoni rudironsoni force-pushed the fix/opencode-mcp-local branch from 3c7b967 to b3ac8bf Compare June 30, 2026 20:48
Copilot AI review requested due to automatic review settings July 1, 2026 07:41
@rudironsoni rudironsoni force-pushed the fix/opencode-mcp-local branch from b3ac8bf to 3f5ab5e Compare July 1, 2026 07:41
@rudironsoni

Copy link
Copy Markdown
Contributor Author

@JerrettDavis can we merge?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes the OpenCode integration’s MCP configuration by switching from an invalid remote /mcp HTTP endpoint to a local stdio MCP server (headroom mcp serve), and aligns MCP install/status/uninstall behavior around the registrar lifecycle so OpenCode is covered consistently.

Changes:

  • Update MCP uninstall/status commands to delegate to the MCP registrar set (instead of Claude-only config/CLI handling).
  • Remove OpenCode provider-config injection that wrote a generated Headroom MCP block into opencode.json (MCP persistence is handled via registrars / wrap behavior instead).
  • Add/adjust regression tests and update docs/changelog for the corrected OpenCode MCP wiring.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/test_cli/test_wrap_opencode.py Adds a regression test ensuring Serena is registered with --context agent during wrap opencode.
tests/test_cli/test_mcp.py Refactors CLI tests to validate uninstall/status via registrars rather than Claude’s legacy config file behavior.
headroom/providers/opencode/config.py Removes the generated Headroom MCP block writer from OpenCode provider config injection helpers.
headroom/cli/mcp.py Changes mcp uninstall and mcp status to enumerate registrars so OpenCode is included.
docs/content/docs/opencode.mdx Updates OpenCode docs to reflect local MCP behavior and provider-scope install guidance.
CHANGELOG.md Adds a changelog entry documenting the OpenCode MCP config fix and install target support.

Comment on lines 24 to 28
| Step | What happens |
|---|---|
| Proxy | Starts the Headroom proxy unless `--no-proxy` is set |
| Provider config | Writes a `headroom` provider using `@ai-sdk/openai-compatible` into `opencode.json` and `OPENCODE_CONFIG_CONTENT`, pointing at `http://127.0.0.1:<port>/v1` |
| Runtime env | Sets `OPENCODE_CONFIG_CONTENT` so OpenCode reads provider, model, plugin, and local MCP config at launch |
| Provider injection | Writes a `headroom` provider using `@ai-sdk/openai-compatible` into `opencode.json`, pointing at `http://127.0.0.1:<port>/v1` |
| Runtime env | Sets `OPENCODE_CONFIG_CONTENT` with provider, plugin, and local MCP config so OpenCode picks up Headroom at launch |
| Provider compatibility | Leaves `OPENAI_BASE_URL` and `ANTHROPIC_BASE_URL` untouched so OpenCode `/connect` providers keep their own routing |
Comment on lines 69 to 72
| Variable | Description |
|---|---|
| `OPENCODE_CONFIG_CONTENT` | JSON payload with provider, model, plugin, and optional local MCP config injected by `wrap` |
| `HEADROOM_PROXY_URL` | Proxy URL passed to the native `headroom-opencode` plugin. Defaults to `http://127.0.0.1:8787` inside the plugin |
| `OPENCODE_CONFIG_CONTENT` | JSON payload with provider, plugin, and optional local MCP config injected by `wrap` |
| `HEADROOM_PROXY_URL` | Proxy URL passed to Headroom MCP when a non-default port is used, and to the native plugin when configured |
| `HEADROOM_CONTEXT_TOOL` | Set to `lean-ctx` to use lean-ctx instead of RTK |
@rudironsoni rudironsoni force-pushed the fix/opencode-mcp-local branch from 3f5ab5e to 962394c Compare July 1, 2026 09:19

@JerrettDavis JerrettDavis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the latest OpenCode docs/table follow-up. The previously-approved local MCP behavior is unchanged; the final commit restores the markdown table separators and keeps the runtime config wording accurate as optional MCP config. CI is green on the latest head, and I do not see a remaining code-review blocker.

@rudironsoni rudironsoni force-pushed the fix/opencode-mcp-local branch from e91671d to d6bbda1 Compare July 2, 2026 08:47
@rudironsoni rudironsoni force-pushed the fix/opencode-mcp-local branch from d6bbda1 to d3b5d97 Compare July 3, 2026 09:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: needs author action Pull request body or readiness checklist still needs author updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenCode integration writes invalid MCP config across wrap, mcp install, and persistent install

3 participants