Skip to content

Refresh Claude MCP auth via headersHelper (no mid-session expiry)#200

Closed
sunishsheth2009 wants to merge 1 commit into
databricks:mainfrom
sunishsheth2009:sunish-sheth_data/mcp-token-refresh
Closed

Refresh Claude MCP auth via headersHelper (no mid-session expiry)#200
sunishsheth2009 wants to merge 1 commit into
databricks:mainfrom
sunishsheth2009:sunish-sheth_data/mcp-token-refresh

Conversation

@sunishsheth2009

Copy link
Copy Markdown

Summary

Fixes a mid-session MCP auth expiry for Claude Code. Claude freezes an MCP header's ${OAUTH_TOKEN} at config-load time, so a session running past the Databricks OAuth token lifetime (~1h) sees MCP calls fail with 401/403 until relaunch. This registers Claude's Databricks MCP servers with a headersHelper command instead of a static header — Claude re-runs it on every connect/reconnect and automatically on a 401/403 retry (v2.1.193+), so the bearer is always fresh.

Changes

  • ucode auth-token --mcp-header — new flag that emits the JSON {\"Authorization\": \"Bearer <token>\"} object Claude's headersHelper consumes, reusing the existing cross-platform token logic (OAuth refresh / PAT / DATABRICKS_BEARER short-circuit). Bare-token output (for the model apiKeyHelper and Codex) is unchanged.
  • build_claude_mcp_entry(url, headers_helper) — a Claude HTTP MCP entry that uses headersHelper rather than a static Authorization header.
  • configure_mcp_command builds the helper command (build_auth_shell_command(..., mcp_header=True)ucode auth-token --mcp-header) when Claude is a target client, and threads it through apply_mcp_server_changesconfigure_client_mcp_server. Every other client keeps the launch-set ${OAUTH_TOKEN} header (unchanged).

Other clients — investigated, documented as a known limitation

I checked Codex, Gemini, OpenCode, and Copilot for an equivalent self-refresh hook:

Client MCP auth options Self-refresh possible?
Claude Code static header, env-var, headersHelper (command), OAuth/DCR ✅ this PR
Codex bearer_token_env_var, static http_headers, OAuth-login (DCR) ❌ no per-request command hook; DCR blocked by Databricks
Gemini / OpenCode / Copilot static header / env-ref only ❌ no per-request hook

None of the others expose a per-request auth command for MCP, and a parent process can't mutate a running child's env var — so there's no clean self-refresh for them. Documented in the README; a long session on those clients needs relaunch to pick up a fresh token (pre-existing behavior, unchanged here).

Verification

Against a live workspace: ucode auth-token --mcp-header emits a valid OAuth JSON header, and MCP initialize returns 200 with that exact header — confirming the helper mints a working, fresh token. Unit tests: build_claude_mcp_entry shape, the Claude branch using headersHelper when provided (and falling back to the static entry otherwise), and the --mcp-header JSON output. 142 tests pass across test_mcp.py + test_cli.py; ruff check clean.

This pull request and its description were written by Isaac.

Claude Code freezes an MCP header's `${OAUTH_TOKEN}` at config-load time, so a
session running past the Databricks OAuth token lifetime (~1h) sees MCP calls
start failing until relaunch. Register Claude's MCP servers with a `headersHelper`
command instead: Claude re-runs it on every connect/reconnect and on a 401/403
retry, so the bearer is always fresh.

- `ucode auth-token --mcp-header`: new flag that emits the JSON
  `{"Authorization": "Bearer <token>"}` object Claude's headersHelper expects,
  reusing the existing cross-platform token logic (OAuth refresh / PAT / bearer
  short-circuit). Bare-token output (for apiKeyHelper / codex) is unchanged.
- `build_claude_mcp_entry(url, headers_helper)`: Claude HTTP MCP entry using
  `headersHelper` rather than a static `Authorization` header.
- `configure_mcp_command` builds the helper command (via `build_auth_shell_command
  (..., mcp_header=True)`) when Claude is among the target clients and threads it
  through `apply_mcp_server_changes` -> `configure_client_mcp_server`. Other
  clients keep the launch-set `${OAUTH_TOKEN}` header.

Investigated Codex/Gemini/OpenCode/Copilot: none expose a per-request auth hook
for MCP (only static headers or env-var bearer), and a parent can't mutate a
running child's env, so there's no clean self-refresh for them — documented as a
known limitation in the README.

Verified against a live workspace: `ucode auth-token --mcp-header` emits a valid
OAuth JSON header and MCP `initialize` returns 200 with it. Tests: 142 pass in
test_mcp.py + test_cli.py; ruff clean.

Co-authored-by: Isaac
@sunishsheth2009

Copy link
Copy Markdown
Author

Superseded by #201, which routes all MCP clients (including Claude) through the uniform ucode mcp-proxy stdio bridge. The proxy mints a fresh OAuth token per request, so Claude's MCP auth self-refreshes without the client-specific headersHelper this PR added. Closing in favor of the uniform approach.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant