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

Aperta
#93,595 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
area:mcp area:plugins bug has repro platform:windows regression
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

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.