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
- Lingua principale
- Python
- Stelle
- 145k
- Fork
- 23.1k
- Metriche di merge delle PR
- Metriche PR in attesa
Descrizione
✨ 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.
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Direzione di ricerca
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.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Ambito
- api, cli
- Tipo di issue
- Bug
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Stato di attività
- Attiva
- Chiarezza
- Abbastanza chiara
- Idoneità per principianti
- 48/100