Plugin MCP relative headersHelper resolved against session CWD, not plugin root

Resolved 💬 2 comments Opened Jun 13, 2026 by dblundell Closed Jun 22, 2026

Summary

Claude Code resolves a plugin MCP server's relative headersHelper path against the session's current working directory instead of the plugin root. As a result, a plugin's auth-header helper is only found when the session is launched from the plugin's own directory; from any other cwd the helper isn't located, no auth header is produced, and the MCP server fails to connect.

Separately, ${CLAUDE_PLUGIN_ROOT} is not expanded in the headersHelper field, so plugins cannot portably reference their own helper as a workaround.

There is also a security implication: because resolution is cwd-relative, Claude Code will execute whatever ./scripts/get-headers.sh happens to exist in the directory the session was launched from.

Environment

  • Claude Code 2.1.177 (cli). Also reproduced at 2.1.158 and 2.1.159, so this is persistent, not a recent regression.
  • Platform: macOS (darwin), Node v24.3.0.
  • A plugin declaring an HTTP MCP server with a headersHelper, e.g.:

``json
"mcpServers": {
"demo": {
"type": "http",
"url": "http://127.0.0.1:8765/mcp",
"headersHelper": "scripts/get-headers.sh"
}
}
``

Expected behavior

A relative headersHelper should resolve against the plugin root — i.e. ${CLAUDE_PLUGIN_ROOT}/<headersHelper> — independent of the session cwd, consistent with how plugin hook commands already resolve ${CLAUDE_PLUGIN_ROOT}. Additionally, ${CLAUDE_PLUGIN_ROOT} should be expanded if present in the headersHelper value.

Actual behavior

  • A relative headersHelper is resolved against the session cwd. Launched from a directory without that relative path, the helper spawn fails (ENOENT).
  • The MCP debug log shows Executing headersHelper to get dynamic headers immediately followed by Error getting headers from headersHelper: headersHelper for MCP server '<name>' did not return a valid value, a failure in ~12 ms, then a fall-through to OAuth discovery and a failed connection.
  • ${CLAUDE_PLUGIN_ROOT} written literally into headersHelper is not expanded.

Reproduction

  1. Install a plugin whose .claude-plugin/plugin.json declares an HTTP MCP server with a relative headersHelper: "scripts/get-headers.sh", where scripts/get-headers.sh (in the plugin root) prints {"Authorization": "Bearer ..."}.
  2. Start Claude Code with cwd = any directory other than the plugin root (e.g. /tmp).
  3. The MCP server fails to connect; the per-server MCP debug log shows headersHelper ... did not return a valid value.
  4. Start Claude Code with cwd = the plugin root (which contains ./scripts/get-headers.sh) → it connects. This cwd-dependence is the tell.

Proof it is cwd-relative (not an env / $HOME issue)

Placing a probe helper at <cwd>/scripts/get-headers.sh that logs its invocation and delegates to the real helper: Claude Code executed that cwd-local file and logged cwd=/tmp — confirming it resolved scripts/get-headers.sh against the session cwd. (An earlier $HOME-unset hypothesis was ruled out — the captured invocation showed HOME was present.)

Impact

  • Any plugin that authenticates an HTTP/SSE MCP server via a relative headersHelper works only when launched from the plugin root — broken for normal use from other working directories.
  • An absolute-path workaround written into the installed manifest is reverted by /plugin update (which reinstalls the committed relative path), so it must be re-applied after every update.

Security implication

Cwd-relative resolution means Claude Code will execute an attacker-supplied ./scripts/get-headers.sh if a session is launched inside a directory that contains one (e.g. a cloned repo). The helper runs as the user at connect time. Resolution should be anchored to the trusted plugin root, never the (potentially untrusted) cwd.

Suggested fix

  1. Resolve a relative headersHelper against the plugin root (${CLAUDE_PLUGIN_ROOT}), consistent with hook command resolution — not the session cwd.
  2. Expand ${CLAUDE_PLUGIN_ROOT} in the headersHelper value.
  3. (Security) Never resolve plugin-declared executables against the session cwd.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗