Skip to content

mcp: {env:...} in remote server headers not expanded (401 unauthorized) #56125

Closed as not planned

Description

@elpiaffo

Summary

{env:...} placeholders are not expanded inside a remote MCP server's headers values. The server receives an empty/invalid bearer token, so token-authenticated remote MCP servers fail with HTTP 401. A literal token in the same headers map works, so the connection and oauth: false handling are fine — only env substitution in headers is broken.

Environment

  • opencode version: 2.0.31
  • OS: Microsoft Windows NT 10.0.19045.0 (win32 x64)
  • Terminal: Windows (PowerShell launched from OpenCode); terminal env vars unset
  • Shell: C:\WINDOWS\system32\cmd.exe (COMSPEC); reproduction commands run via PowerShell
  • Install/channel: latest
  • Active plugins: none found in config

Reproduction

  1. Configure a remote MCP server that requires a bearer token, using env substitution in the header (~/.config/opencode/opencode.jsonc):

    {
      "$schema": "https://opencode.ai/config.json",
      "mcp": {
        "servers": {
          "kagi": {
            "type": "remote",
            "url": "https://mcp.kagi.com/mcp",
            "oauth": false,
            "headers": { "Authorization": "Bearer {env:KAGI_API_KEY}" }
          }
        }
      }
    }
  2. Set KAGI_API_KEY and confirm it is present in the process environment (verified: non-empty, correct length).

  3. Run opencode mcp list.

Expected Behavior

✓ kagi connected

Actual Behavior

✗ kagi  failed: Error POSTing to endpoint: {"error": "invalid_token", "error_description": "Authentication failed. The provided bearer token is invalid, expired, or no longer recognized by the server. ..."} (HTTP 401)

Server-side log for the attempt:

level=WARN message="mcp http authentication rejected" status=401 server=kagi method=POST origin=https://mcp.kagi.com
level=WARN message="mcp connect failed" server=kagi status.status=failed

The same request sent directly (PowerShell Invoke-WebRequest with Authorization: Bearer <key>) returns HTTP 200.

Additional Context

  • Literal token works. Replacing the header value with the literal token connects successfully:
    "Authorization": "Bearer <token>" → level=INFO message="mcp connected" server=kagi tools=2.

  • Whole-value substitution also fails. A header value of exactly "{env:KAGI_AUTH}" (with KAGI_AUTH="Bearer <key>" set in the environment) also returns 401, so it is not specific to the Bearer prefix.

  • The variable is present in the environment where opencode mcp list runs; this is not a missing-variable issue.

  • Redacted config that fails:

    "headers": { "Authorization": "Bearer {env:KAGI_API_KEY}" }

    Redacted config that works:

    "headers": { "Authorization": "Bearer <REDACTED>" }
  • Reproducible consistently (multiple opencode mcp list runs).

  • Workaround in use: literal token in headers.

  • Possibly related to issue Remote MCP with custom headers returning 401 Unauthorized despite oauth: false #30428 ("Remote MCP with custom headers returning 401 Unauthorized").

Activity

  1. opencode-agent commented on Oct 16, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I tried this on 2.0.31 (Linux) with your config, pointed at a local mock MCP server that logs the Authorization header it receives:

    • Service started from a shell where KAGI_API_KEY was set: ✓ kagi connected, and the header arrived as Bearer <token>. A whole-value "{env:KAGI_AUTH}" worked too.
    • Service already running without the variable, then opencode mcp list from a shell that has it: ✗ kagi failed: ... (HTTP 401), which is exactly your error. The server received Authorization: Bearer with an empty token.

    The reason is that opencode mcp list doesn't load the config itself. It asks the background service, and the service fills in {env:...} from its own environment. An unset variable silently becomes an empty string (#33853). So if the service was already running before KAGI_API_KEY was set, or was started some other way (for example by the desktop app or at login), setting the variable in PowerShell won't reach it.

    Could you try one of these and tell us whether it fixes it?

    1. Store the variable for the service, then restart it:
      opencode service set env KAGI_API_KEY <your key>
      opencode service restart
      opencode mcp list
      
    2. Or run opencode service restart from a PowerShell window where KAGI_API_KEY is set, then run opencode mcp list.

    If it still fails after that, please tell us how the service is started (CLI, desktop app, something else), the output of opencode service get env (with the value redacted), and whether opencode api get /api/config shows an empty token after Bearer for kagi. I could only test on Linux, so details like these would help narrow down anything Windows-specific.

    Related issues, ordered by likelihood:

  2. opencode-agent commented on Oct 19, 2026

    @opencode-agent
    Contributor

    Closing this for now since we haven't heard back. On 2.0.31, {env:...} in remote MCP headers worked in my tests whenever the background service had the variable. I only got your exact 401 (an empty token after Bearer) when the service had been started without KAGI_API_KEY. Variables set in PowerShell don't reach a service that's already running.

    If you haven't yet, try opencode service set env KAGI_API_KEY <your key> and then opencode service restart. If it still fails after that, please reply with how the service is started and the output of opencode service get env (value redacted), and we can reopen this. My tests were on Linux only, so anything Windows-specific would help. Meanwhile #33853 tracks warning about unset {env:...} variables instead of silently using an empty string. Thanks for the detailed report!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions