@@ -159,16 +159,29 @@ const (
159159// path (e.g. "/apiKeyHelper" or "/env/ANTHROPIC_BASE_URL"). Dots in flat
160160// top-level key names (e.g. "/cursor.general.openAIBaseURL") are treated as
161161// literals by hujson.Patch.
162- // ValueField names which LLMApplyConfig field to write: "GatewayURL",
163- // "ProxyBaseURL", "TokenHelperCommand", "PlaceholderAPIKey" (constant "thv-proxy"),
164- // or "NodeTLSRejectUnauthorized" (writes "0" when TLSSkipVerify is true).
165- // ClearWhenEmpty: when true and the resolved value is empty, the key is removed
166- // from the settings file rather than skipped. Use for conditional keys like
167- // NODE_TLS_REJECT_UNAUTHORIZED that must be cleaned up when the flag is cleared.
162+ //
163+ // Exactly one of ValueField or Literal must be set:
164+ // - ValueField names which ApplyConfig field to write. Valid values:
165+ // "GatewayURL", "ProxyBaseURL", "ProxyOrigin", "TokenHelperCommand",
166+ // "PlaceholderAPIKey", "NodeTLSRejectUnauthorized".
167+ // An unrecognised ValueField is a programming error and causes
168+ // ConfigureLLMGateway to return an error.
169+ // - Literal is written verbatim into the settings key (e.g. a fixed auth
170+ // type string). Use Literal instead of ValueField for constant values so
171+ // that typos in ValueField are caught as errors rather than silently
172+ // written as unexpected strings.
173+ //
174+ // ClearWhenEmpty: when true and the resolved value is empty, the key is
175+ // removed from the settings file rather than skipped. Use for conditional
176+ // keys like NODE_TLS_REJECT_UNAUTHORIZED that must be cleaned up when the
177+ // flag is cleared. Ignored when Literal is set (literals are never empty).
168178type LLMGatewayKeySpec struct {
169- JSONPointer string // RFC 6901 path
170- ValueField string // "GatewayURL" | "ProxyBaseURL" | "TokenHelperCommand" | "PlaceholderAPIKey" | "NodeTLSRejectUnauthorized"
171- ClearWhenEmpty bool // remove the key when the resolved value is empty
179+ JSONPointer string // RFC 6901 path
180+ // ValueField: "GatewayURL" | "ProxyBaseURL" | "ProxyOrigin" |
181+ // "TokenHelperCommand" | "PlaceholderAPIKey" | "NodeTLSRejectUnauthorized"
182+ ValueField string
183+ Literal string // constant value written verbatim; mutually exclusive with ValueField
184+ ClearWhenEmpty bool // remove the key when the resolved value is empty (ignored for Literal)
172185}
173186
174187// clientAppConfig represents a configuration path for a supported MCP client.
@@ -223,6 +236,10 @@ type clientAppConfig struct {
223236 // LLMGatewayKeys lists the JSON Pointer paths and value-field mappings to
224237 // apply when setting up (or reverting) LLM gateway access.
225238 LLMGatewayKeys []LLMGatewayKeySpec
239+ // LLMSetupNote is an optional message printed to stdout after a successful
240+ // "thv llm setup" for this client. Use it to surface manual steps that
241+ // toolhive cannot automate (e.g. env vars the tool reads from a .env file).
242+ LLMSetupNote string
226243}
227244
228245// extractServersKeyFromConfig extracts the servers key from MCPServersPathPrefix
@@ -839,17 +856,29 @@ var supportedClientIntegrations = []clientAppConfig{
839856 SupportsSkills : true ,
840857 SkillsGlobalPath : []string {".agents" , skillsDirName },
841858 SkillsProjectPath : []string {".agents" , skillsDirName },
842- // LLM gateway: patches the same settings.json used for MCP
843- LLMGatewayMode : "direct" ,
859+ // LLM gateway: patches the same settings.json used for MCP.
860+ // Gemini CLI has no dynamic token-command equivalent, so it uses the
861+ // proxy path. GOOGLE_GEMINI_BASE_URL is only honoured when
862+ // security.auth.selectedType is "gemini-api-key" (fixed in
863+ // gemini-cli v0.40.0, PR #25357); OAuth auth ignores the override.
864+ //
865+ // NODE_TLS_REJECT_UNAUTHORIZED is intentionally omitted: in proxy mode
866+ // the tool connects to the local proxy over plain HTTP, so there is no
867+ // TLS handshake on that leg. Setting the env var would globally disable
868+ // TLS verification for all other HTTPS requests the Gemini CLI process
869+ // makes, which is an unacceptable side-effect.
870+ LLMGatewayMode : "proxy" ,
844871 LLMBinaryName : "gemini" ,
845872 LLMSettingsFile : "settings.json" ,
846873 LLMSettingsRelPath : []string {".gemini" },
847874 LLMGatewayKeys : []LLMGatewayKeySpec {
848- {JSONPointer : "/auth/tokenCommand" , ValueField : "TokenHelperCommand" },
849- {JSONPointer : "/baseUrl" , ValueField : "GatewayURL" },
850- // NODE_TLS_REJECT_UNAUTHORIZED is only written when --tls-skip-verify is set.
851- // ClearWhenEmpty ensures it is removed when the flag is later cleared.
852- {JSONPointer : "/env/NODE_TLS_REJECT_UNAUTHORIZED" , ValueField : "NodeTLSRejectUnauthorized" , ClearWhenEmpty : true },
875+ // Force API-key auth so GOOGLE_GEMINI_BASE_URL is respected.
876+ {JSONPointer : "/security/auth/selectedType" , Literal : "gemini-api-key" },
877+ // Placeholder API key (proxy does not validate it).
878+ {JSONPointer : "/env/GEMINI_API_KEY" , Literal : llmPlaceholderAPIKey },
879+ // Proxy origin for Gemini CLI; path is stripped because Gemini
880+ // CLI appends its own /v1beta/... path to the base URL.
881+ {JSONPointer : "/env/GOOGLE_GEMINI_BASE_URL" , ValueField : "ProxyOrigin" },
853882 },
854883 },
855884 {
0 commit comments