anthropics / anthropics/claude-code

[BUG] Plugin HTTP MCP ${VAR} header expansion resolves to empty in 2.1.260 (works in 2.1.247 / 2.1.223) — bearer token never sent

オープン
#93,595 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る
area:mcp area:plugins bug has repro platform:windows regression
主要言語
Python
スター
145k
フォーク
23.1k
PR マージ指標
PR 指標を取得中

説明

✨ The following was generated via Claude, mistakes do happen.

### Preflight Checklist

- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code

### What's Wrong?

A plugin-defined HTTP MCP server whose `Authorization` header uses `${VAR}` substitution fails to connect on **2.1.260** with HTTP 401, because the header is sent with no token in it. The identical plugin, environment variable, shell and config connect successfully on **2.1.247** and **2.1.223**.

This bites desktop-app users hardest, because the Windows desktop app bundles its own CLI under `%APPDATA%\Claude\claude-code\\` — currently 2.1.260 — while a separate npm-global install on `PATH` may be on a working version. The result looks like a desktop-vs-terminal inconsistency, but it is not: **2.1.260 reproduces the failure when launched directly from a plain shell**, with the desktop app out of the picture entirely.

Plugin manifest under test (redacted plugin name; any plugin with the same shape will do):

```json
"mcpServers": {
"sonarqube": {
"type": "http",
"url": "https://api.sonarcloud.io/mcp",
"headers": {
"Authorization": "Bearer ${SONAR_TOKEN}",
"SONARQUBE_ORG": "example-org"
}
}
}
```

**Version bisect — one machine, one shell, one config, one env var:**

| CLI build | Origin | `claude mcp list` result |
| --- | --- | --- |
| 2.1.223 | npm-global on `PATH` | ✔ Connected |
| 2.1.247 | older desktop-bundled build, still on disk | ✔ Connected |
| **2.1.260** | current desktop-bundled build | ✘ Failed to connect — HTTP 401 |

All three were invoked from the same Bash session, in which `SONAR_TOKEN` is confirmed present. That isolates the variable to the CLI binary.

**Evidence the token is valid and the header arrives empty.** Three direct POSTs to the same endpoint from that same shell:

| `Authorization` value | HTTP |
| --- | --- |
| `Bearer $SONAR_TOKEN` (expanded by the shell) | **200** |
| `Bearer ${SONAR_TOKEN}` (literal, unexpanded) | 403 |
| `Bearer ` (empty) | **401** |

The 401 returned by 2.1.260 matches the *empty* case, not the unexpanded-literal case — so this is not the trailing-brace corruption described in #84367. Note the limitation: the server returns the same 401 whether the header is empty or absent, and `claude mcp get` redacts the value on both builds, so I cannot distinguish "expanded to empty string" from "header dropped" from the client side. Either way the token does not leave the machine.

### What Should Happen?

`${SONAR_TOKEN}` in a plugin manifest's `mcpServers.*.headers` should resolve from the CLI process environment on 2.1.260 exactly as it does on 2.1.247 and 2.1.223, and the server should connect.

### Error Messages/Logs

```
plugin:example-plugin:sonarqube: https://api.sonarcloud.io/mcp (HTTP) - ✘ Failed to connect —
Server rejected the configured Authorization header (HTTP 401). Check that the token is valid
for this MCP endpoint — OAuth fallback is disabled when headers.Authorization is set.
Error detail: Streamable HTTP error: Error POSTing to endpoint:
{"jsonrpc":"2.0","id":null,"error":{"code":-32000,"message":"SonarQube token required.
Provide via Authorization: Bearer header."}}
```

### Steps to Reproduce

1. Install a plugin whose manifest declares a `type: "http"` MCP server with `"Authorization": "Bearer ${SOME_TOKEN}"` in `headers`.
2. Set `SOME_TOKEN` as a user-scope environment variable to a value the endpoint accepts, and open a fresh shell so it is inherited.
3. Confirm the credential itself works from that shell, independently of Claude Code:
`curl -s -o /dev/null -w "%{http_code}" -X POST -H "Authorization: Bearer $SOME_TOKEN" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"diag","version":"1"}}}'`
→ 200
4. From that same shell, run `claude mcp list` using a 2.1.247 or 2.1.223 binary → server reports **Connected**.
5. From that same shell, run `claude mcp list` using the 2.1.260 binary (on Windows: `%APPDATA%\Claude\claude-code\2.1.260\claude.exe`) → server reports **Failed to connect (HTTP 401)**.

No config, plugin version, token or environment change between steps 4 and 5 — only the binary.

### Claude Model

Opus (claude-opus-5) — not model-dependent; `claude mcp list` reproduces it with no model involved.

### Is this a regression?

Yes

### Last Working Version

2.1.247

### Claude Code Version

2.1.260

### Platform

Claude Max subscription (desktop app + npm-global CLI)

### Operating System

Windows 11 Pro 10.0.26200

### Terminal/Shell

Claude desktop app (bundled CLI) and Git Bash / cmd

### Additional Information

- Regression is bracketed to **2.1.248 – 2.1.260**; 2.1.247 is the last build confirmed working here.
- The plugin manifest is byte-identical across every cached version of that plugin tested, and there is no competing `mcpServers` entry in `~/.claude.json`, so config drift is ruled out.
- No profile script (`.bashrc`, `.bash_profile`, `.profile`, PowerShell `$PROFILE`) defines the variable — it is inherited from the process environment in every case.
- Possibly related but distinct: #84367 (trailing brace corrupts the header → HTTP 400, on 2.1.222 which works here), #84314 (non-deterministic `${VAR}` expansion for MCP `env` on Linux), #90074 (Windows desktop sanitizes the stdio MCP environment — ruled out here, since 2.1.260 fails with the variable present in its own environment), #91307 (Gitea MCP auth differs Desktop vs Terminal on macOS — that one is framed as host-app state, whereas this reproduces from a plain shell).
- Practical impact: every plugin-distributed HTTP MCP server that authenticates via `${VAR}` headers is silently unusable for desktop-app users on 2.1.260, and the error text ("Check that the token is valid") points investigation at the credential rather than at the client.

コントリビューションガイド

このリポジトリのコントリビューションガイドは索引されていません

調査の方向性

Start with the plugin manifest's mcpServers.*.headers configuration and reproduce with claude mcp list using the 2.1.247 and 2.1.260 binaries, keeping the shell, environment variable, and endpoint unchanged. Trace the HTTP MCP header expansion path; done means ${SOME_TOKEN} sends the token and the server reports Connected on 2.1.260, without regressing the working versions.

索引モデルが issue の本文から書いたものです。

評価

領域
api, cli
issue の種類
バグ
難易度
4/5
見積もり時間
3〜5日
活発さ
活発
明瞭さ
おおむね明確
初心者へのやさしさ
48/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。