Skip to content

feat(opencode): add env variable format translation for MCP config#1667

Merged
dyoshikawa merged 4 commits into
dyoshikawa:mainfrom
zdrapela:fix/opencode-mcp-env-var-and-oauth
May 23, 2026
Merged

feat(opencode): add env variable format translation for MCP config#1667
dyoshikawa merged 4 commits into
dyoshikawa:mainfrom
zdrapela:fix/opencode-mcp-env-var-and-oauth

Conversation

@zdrapela
Copy link
Copy Markdown
Contributor

@zdrapela zdrapela commented May 22, 2026

Summary

OpenCode uses {env:VAR} format for environment variable references in its MCP config (docs, while rulesync's canonical format uses ${VAR}. This PR adds bidirectional translation and extracts a shared env var conversion utility used by both OpenCode and Cursor.

I ran into an issue while configuring SourceBot with API key, where this config didn't get translated into working MCP config for OpenCode.

    "sourcebot": {
      "description": "Internal Red Hat code search via Sourcebot (requires VPN)",
      "type": "http",
      "url": "https://sourcebot.XXX.com/api/mcp",
      "headers": {
        "Authorization": "Bearer ${SOURCEBOT_API_KEY}"
      }

Changes

src/features/mcp/mcp-env-var-format.ts (new)

  • Shared utility with convertEnvVarRefsFromToolFormat() and convertEnvVarRefsToToolFormat()
  • Translates env var references in both env and headers fields across all MCP servers
  • CANONICAL_ENV_VAR_PATTERN regex avoids double-converting values already in ${env:} form

src/features/mcp/mcp-env-var-format.test.ts (new)

  • 10 dedicated tests using a generic {tool:VAR} pattern, independent of any specific tool
  • Covers: env values, headers values, multiple refs, no-env servers, multiple servers, double-convert avoidance, round-trip

src/features/mcp/opencode-mcp.ts

  • Add OPENCODE_ENV_VAR_PATTERN regex for matching {env:VAR} (with negative lookbehind to avoid matching Cursor's ${env:VAR})
  • Wire up shared utility in fromRulesyncMcp() (export: ${VAR} -> {env:VAR}) and toRulesyncMcp() (import: {env:VAR} -> ${VAR})

src/features/mcp/cursor-mcp.ts

  • Replace inline env var conversion functions with shared utility calls
  • Cursor now also translates env var refs in headers (was env-only before)

Design Decisions

  • Shared utility: Extracted common env var conversion logic into mcp-env-var-format.ts to eliminate duplication between OpenCode and Cursor. Each tool only provides its tool-specific regex pattern and replacement string.
  • Headers translation for both tools: Both OpenCode and Cursor now translate env var refs in headers, since remote MCP servers use env var references in headers (e.g., Authorization: Bearer {env:API_KEY}).
  • Negative lookbehind: The OpenCode import regex (?<!\$)\{env:...} avoids matching Cursor's ${env:VAR} format, preventing cross-tool interference.
  • Consistent capture groups: Both import and export regexes use [^}:]+ to exclude colons, ensuring round-trip consistency.
  • Test layering: Shared utility has dedicated tool-agnostic tests; tool-specific files keep only integration tests that verify wiring and full-pipeline behavior.

How it was tested

  • 21 new tests across 3 files (10 unit + 11 integration) — all pass
  • pnpm cicheck passes locally (format, lint, typecheck)
  • CI passes (Code Quality, Build, E2E on ubuntu/macOS/macOS-intel/Windows)
  • 3 rounds of /review-pr with parallel code-review and security-review agents — all mid/high findings fixed, final iteration has only low-severity findings remaining

@zdrapela zdrapela force-pushed the fix/opencode-mcp-env-var-and-oauth branch from 9d16a1d to a99d931 Compare May 22, 2026 11:13
OpenCode uses {env:VAR} format for environment variable references,
while rulesync canonical format uses \${VAR}. Add bidirectional
translation in convertToOpencodeFormat and convertFromOpencodeFormat,
covering both environment (local servers) and headers (remote servers).

This mirrors the existing env var translation implemented for Cursor
(\${env:VAR} <-> \${VAR}) but with OpenCode's distinct format
({env:VAR}, no \$ prefix).

Assisted-by: OpenCode
@zdrapela zdrapela force-pushed the fix/opencode-mcp-env-var-and-oauth branch from a99d931 to c7f06c6 Compare May 22, 2026 11:24
zdrapela and others added 3 commits May 22, 2026 14:05
Extract duplicated env var format conversion logic from opencode-mcp.ts
and cursor-mcp.ts into a shared mcp-env-var-format.ts utility. This
eliminates the DRY violation where both files had structurally identical
regex-based record transformation patterns.

Also fixes:
- Cursor now translates env var refs in headers (was env-only)
- Tightened OpenCode import regex capture from [^}]+ to [^}:]+ for
  consistency with the export regex
- Added edge case tests: cross-tool interop, server-without-env,
  and Cursor headers env var conversion

Assisted-by: Claude Code
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add mcp-env-var-format.test.ts with 10 tests covering both exported
functions independently using a generic {tool:VAR} pattern, ensuring
the utility works correctly for any future tool integration.

Assisted-by: Claude Code
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove 4 tests now covered by the shared mcp-env-var-format.test.ts:
- "multiple env variables embedded in strings" from both cursor and opencode
- "server without env field" from cursor
- "server without env or headers fields" from opencode

Assisted-by: Claude Code
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zdrapela zdrapela marked this pull request as ready for review May 22, 2026 12:19
Copy link
Copy Markdown
Collaborator

@dyoshikawa-claw dyoshikawa-claw left a comment

Choose a reason for hiding this comment

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

LGTM. All CI checks pass and all findings from code/security review are low severity. Well-designed shared utility extraction.

@dyoshikawa dyoshikawa merged commit dfc4fca into dyoshikawa:main May 23, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants