stacklok / stacklok/toolhive

thv run local proxy doesn't advertise itself as OAuth protected resource, breaking MCP client re-authentication

Open
#4,124 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

authentication bug cli proxy
Dominant language
Go
Stars
2.2k
Forks
300
Avg merge
1d 15h
Merged PRs (30d)
184

Description

Bug Description

When using thv run to create a local proxy for a remote MCP server that uses OAuth, the initial authentication works fine. However, when the MCP client triggers re-authentication (e.g., after token expiry), it fails because the local proxy doesn't serve /.well-known/oauth-protected-resource with its own URL. The MCP SDK discovers the remote server's metadata instead, causing an origin mismatch.

Error

Error: Protected resource https://mcp.stacklok.dev/github/mcp does not match expected http://localhost:41339/github/mcp (or origin)

Steps to Reproduce

  1. Start a local proxy with thv run:

    thv run --name github-test --transport streamable-http https://mcp.stacklok.dev/github/mcp
    

    This creates a local proxy at http://localhost:<PORT>/github/mcp.

  2. The initial OAuth authentication flow completes successfully and the proxy is usable.

  3. When re-authentication is triggered (e.g., via /mcp reauth in Claude Code), the MCP SDK fetches /.well-known/oauth-protected-resource from the local proxy.

  4. The local proxy doesn't serve this endpoint (returns 404), so the SDK falls back to discovering the remote server's metadata, which returns https://mcp.stacklok.dev/github/mcp as the resource.

  5. The SDK compares this against the server URL it connected to (http://localhost:<PORT>/github/mcp), finds an origin mismatch, and rejects it.

Working Comparison

Connecting directly to the remote server (bypassing thv run) works correctly:

claude mcp add --scope user --transport http github-test https://mcp.stacklok.dev/github/mcp

Re-authentication succeeds because the resource field matches the server URL the client connected to.

Root Cause

In pkg/auth/token.go, NewAuthInfoHandler returns a 404 when resourceURL is empty:

if resourceURL == "" {
    w.WriteHeader(http.StatusNotFound)
    return
}

When thv run is used for remote servers, TokenValidatorConfig.ResourceURL defaults to empty, so the local proxy never serves RFC 9728 protected resource metadata. The MCP SDK then discovers the remote server's metadata, whose resource field doesn't match the local proxy origin.

Suggested Fix

thv run should automatically set ResourceURL to the local proxy's own URL (e.g., http://localhost:<PORT>/path) when creating the proxy for a remote server, so that /.well-known/oauth-protected-resource correctly identifies the local proxy as the resource.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with pkg/auth/token.go and the NewAuthInfoHandler behavior when resourceURL is empty, then trace the thv run proxy setup and TokenValidatorConfig.ResourceURL for remote servers. Done means the local proxy serves /.well-known/oauth-protected-resource with its own URL and MCP client re-authentication no longer fails with an origin mismatch.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, authentication
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.