Summary
On CLI thv run, two OIDC flags are accepted but do nothing:
--oidc-insecure-allow-http ("Allow HTTP (non-HTTPS) OIDC issuers for local
development/testing")
--thv-ca-bundle ("Path to CA certificate bundle for ToolHive HTTP operations
(JWKS, OIDC discovery, etc.)")
So there is no working way to point thv run at a local OIDC issuer — plain HTTP,
or HTTPS with a self-signed / private CA. Valid token gets 401 at request time.
One root cause: createOIDCConfig() in cmd/thv/app/run_flags.go builds the
auth.TokenValidatorConfig used for the auth middleware, but never sets
InsecureAllowHTTP or CACertPath. It only sets AllowPrivateIP. Same class as
#1470 ("Pass allowPrivateIP into createOIDCConfig") — that PR fixed
AllowPrivateIP in this function, the other two fields were left out.
Confirmed on v0.46.0 (commit c6c425a924) and still on main (6b40bf3d).
Why
CLI builds OIDC config two times:
WithOIDCConfig(...) (pkg/runner/config_builder.go) gets all fields, including
ThvCABundle and InsecureAllowHTTP. This fills the top-level
RunConfig.OIDCConfig.
setupOIDCConfiguration() → createOIDCConfig() (cmd/thv/app/run_flags.go)
builds a second TokenValidatorConfig. This one goes to
WithMiddlewareFromFlags(...), and it is what builds the auth middleware
config. Here InsecureAllowHTTP and CACertPath are missing.
setupOIDCConfiguration also passes only runFlags.JWKSAllowPrivateIP, not
runFlags.InsecureAllowHTTP and not runFlags.ThvCABundle.
At runtime the proxy rebuilds the auth middleware from the middleware config
(auth.CreateMiddleware → json.Unmarshal(params) → GetAuthenticationMiddleware
→ NewTokenValidator). So it gets InsecureAllowHTTP:false / CACertPath:"", and
OIDC discovery fails in networking.ValidatingTransport.
Proof from the run config
The proxy reads ~/Library/Application Support/toolhive/runconfigs/<name>.json.
OIDC config is there two times and they do not match. The one the proxy uses has
the fields dropped:
top-level oidc_config.InsecureAllowHTTP = true # from WithOIDCConfig
MIDDLEWARE oidc_config.InsecureAllowHTTP (used) = false <-- dropped
top-level oidc_config.CACertPath = /path/to/ca.pem
MIDDLEWARE oidc_config.CACertPath (used) = "" <-- dropped
oidc_config.AllowPrivateIP (both) = true # survives, it is wired (#1470)
AllowPrivateIP survives but the other two do not. That is the tell — it is the
only one of the three that createOIDCConfig sets.
Repro A — --oidc-insecure-allow-http does nothing
- Serve any OIDC issuer over HTTP on localhost (discovery + JWKS), for
example http://localhost:8099 with issuer = http://localhost:8099.
-
thv run --name t --transport stdio \
--oidc-issuer http://localhost:8099 --oidc-audience test \
--oidc-insecure-allow-http=true --jwks-allow-private-ip=true \
<image>
POST /mcp with a valid Authorization: Bearer <token> (token iss/aud
match).
Expected: HTTP is allowed for discovery (flag is set), token validates.
Actual: HTTP 401:
{"error":"invalid_token","error_description":"Invalid token: OIDC discovery failed:
... Get \"http://localhost:8099/.well-known/openid-configuration\": the supplied
URL ... is not HTTPS scheme"}
Repro B — --thv-ca-bundle does nothing
- Serve the same issuer over HTTPS with a self-signed CA cert
(basicConstraints=critical,CA:TRUE, SAN DNS:localhost).
-
thv run --name t --transport stdio \
--oidc-issuer https://localhost:8099 --oidc-audience test \
--jwks-allow-private-ip=true --thv-ca-bundle /path/to/ca.pem \
<image>
POST /mcp with a valid Bearer token.
Expected: CA bundle is trusted for discovery / JWKS, token validates.
Actual: HTTP 401; proxy log:
oidc discovery failed after retries ... Get
"https://localhost:8099/.well-known/openid-configuration":
tls: failed to verify certificate: x509: certificate signed by unknown authority
The --oidc-jwks-url "bypass" from #2288 does not help here either. Issuer
validation and the JWKS fetch use the same client, built without these fields.
Environment
thv v0.46.0 (Commit c6c425a924fac51c86cbade15d0e720e29a600ab), Homebrew
- Runtime: OrbStack (Docker 29.4.0), macOS · Transport:
--transport stdio
- Also checked
main @ 6b40bf3d in source — same createOIDCConfig.
Suggested fix
Same as #1470, just for the other two fields:
createOIDCConfig(...) — add insecureAllowHTTP bool and caCertPath string
params, set InsecureAllowHTTP / CACertPath on the returned
TokenValidatorConfig.
setupOIDCConfiguration(...) — pass runFlags.InsecureAllowHTTP and
runFlags.ThvCABundle into createOIDCConfig.
(AuthTokenFile / --jwks-auth-token-file looks dropped by the same function
too — maybe wire it in the same change.)
Related, but not duplicates
Happy to open a PR for the CLI path.
Summary
On CLI
thv run, two OIDC flags are accepted but do nothing:--oidc-insecure-allow-http("Allow HTTP (non-HTTPS) OIDC issuers for localdevelopment/testing")
--thv-ca-bundle("Path to CA certificate bundle for ToolHive HTTP operations(JWKS, OIDC discovery, etc.)")
So there is no working way to point
thv runat a local OIDC issuer — plain HTTP,or HTTPS with a self-signed / private CA. Valid token gets 401 at request time.
One root cause:
createOIDCConfig()incmd/thv/app/run_flags.gobuilds theauth.TokenValidatorConfigused for the auth middleware, but never setsInsecureAllowHTTPorCACertPath. It only setsAllowPrivateIP. Same class as#1470 ("Pass allowPrivateIP into createOIDCConfig") — that PR fixed
AllowPrivateIPin this function, the other two fields were left out.Confirmed on v0.46.0 (commit
c6c425a924) and still on main (6b40bf3d).Why
CLI builds OIDC config two times:
WithOIDCConfig(...)(pkg/runner/config_builder.go) gets all fields, includingThvCABundleandInsecureAllowHTTP. This fills the top-levelRunConfig.OIDCConfig.setupOIDCConfiguration()→createOIDCConfig()(cmd/thv/app/run_flags.go)builds a second
TokenValidatorConfig. This one goes toWithMiddlewareFromFlags(...), and it is what builds theauthmiddlewareconfig. Here
InsecureAllowHTTPandCACertPathare missing.setupOIDCConfigurationalso passes onlyrunFlags.JWKSAllowPrivateIP, notrunFlags.InsecureAllowHTTPand notrunFlags.ThvCABundle.At runtime the proxy rebuilds the auth middleware from the middleware config
(
auth.CreateMiddleware→json.Unmarshal(params)→GetAuthenticationMiddleware→
NewTokenValidator). So it getsInsecureAllowHTTP:false/CACertPath:"", andOIDC discovery fails in
networking.ValidatingTransport.Proof from the run config
The proxy reads
~/Library/Application Support/toolhive/runconfigs/<name>.json.OIDC config is there two times and they do not match. The one the proxy uses has
the fields dropped:
AllowPrivateIPsurvives but the other two do not. That is the tell — it is theonly one of the three that
createOIDCConfigsets.Repro A —
--oidc-insecure-allow-httpdoes nothingexample
http://localhost:8099withissuer = http://localhost:8099.POST /mcpwith a validAuthorization: Bearer <token>(tokeniss/audmatch).
Expected: HTTP is allowed for discovery (flag is set), token validates.
Actual: HTTP 401:
Repro B —
--thv-ca-bundledoes nothing(
basicConstraints=critical,CA:TRUE, SANDNS:localhost).POST /mcpwith a valid Bearer token.Expected: CA bundle is trusted for discovery / JWKS, token validates.
Actual: HTTP 401; proxy log:
The
--oidc-jwks-url"bypass" from #2288 does not help here either. Issuervalidation and the JWKS fetch use the same client, built without these fields.
Environment
thvv0.46.0 (Commitc6c425a924fac51c86cbade15d0e720e29a600ab), Homebrew--transport stdiomain@6b40bf3din source — samecreateOIDCConfig.Suggested fix
Same as #1470, just for the other two fields:
createOIDCConfig(...)— addinsecureAllowHTTP boolandcaCertPath stringparams, set
InsecureAllowHTTP/CACertPathon the returnedTokenValidatorConfig.setupOIDCConfiguration(...)— passrunFlags.InsecureAllowHTTPandrunFlags.ThvCABundleintocreateOIDCConfig.(
AuthTokenFile/--jwks-auth-token-filelooks dropped by the same functiontoo — maybe wire it in the same change.)
Related, but not duplicates
AllowPrivateIPin this same function. This is thesame thing for the last two fields.
insecureAllowHTTPfor MCPRemoteProxy / theoperator (
spec.oidcConfig). CLIthv run→createOIDCConfigwas not partof it.
insecure_allow_httpfor the authserver upstreamissuer. Different subsystem, not
thv runclient-token validation.Happy to open a PR for the CLI path.