@@ -148,11 +148,30 @@ pub(crate) fn gemini_invocation_for_request(
148148 Ok ( invocation)
149149}
150150
151+ /// Translate one canonical `mcp_servers` entry into the Gemini settings
152+ /// `mcpServers` shape. Gemini reads `url` as an SSE endpoint and `httpUrl`
153+ /// as streamable HTTP, so `{"type": "http"}` entries move their `url` to
154+ /// `httpUrl`; stdio and SSE entries pass through unchanged
155+ /// (`command`/`args`/`env`, `url` + `headers`).
156+ fn gemini_mcp_entry ( entry : & serde_json:: Value ) -> serde_json:: Value {
157+ let Some ( object) = entry. as_object ( ) else {
158+ return entry. clone ( ) ;
159+ } ;
160+ if object. get ( "type" ) . and_then ( serde_json:: Value :: as_str) != Some ( "http" ) {
161+ return entry. clone ( ) ;
162+ }
163+ let Some ( url) = object. get ( "url" ) . cloned ( ) else {
164+ return entry. clone ( ) ;
165+ } ;
166+ let mut translated = object. clone ( ) ;
167+ translated. remove ( "url" ) ;
168+ translated. insert ( "httpUrl" . to_string ( ) , url) ;
169+ serde_json:: Value :: Object ( translated)
170+ }
171+
151172/// Build the settings JSON injected via `GEMINI_CLI_SYSTEM_SETTINGS_PATH`
152173/// when the request carries MCP servers, or `None` when `mcp_servers` is
153- /// absent or empty. The Gemini CLI settings `mcpServers` schema accepts
154- /// the canonical entries unchanged (`command`/`args`/`env` for stdio,
155- /// `type` + `url` + `headers` for remote), so they pass through as-is.
174+ /// absent or empty. Entries are translated via [`gemini_mcp_entry`].
156175/// `existing` is the current system settings document (if any); its keys
157176/// are preserved, with the per-run servers shallow-merged into
158177/// `mcpServers` (per-run entries win on name conflicts).
@@ -171,7 +190,7 @@ fn gemini_settings_with_mcp_servers(
171190 . cloned ( )
172191 . unwrap_or_default ( ) ;
173192 for ( name, entry) in servers {
174- merged. insert ( name. clone ( ) , entry . clone ( ) ) ;
193+ merged. insert ( name. clone ( ) , gemini_mcp_entry ( entry ) ) ;
175194 }
176195 settings. insert ( "mcpServers" . to_string ( ) , serde_json:: Value :: Object ( merged) ) ;
177196 Some ( serde_json:: Value :: Object ( settings) . to_string ( ) )
@@ -468,6 +487,31 @@ mod mcp_settings_tests {
468487 } ,
469488 "linear" : { "type" : "http" , "url" : "https://mcp.linear.app/mcp" }
470489 } ) ;
490+ let request = request_with_mcp_servers ( Some ( servers) ) ;
491+ let settings = gemini_settings_with_mcp_servers ( & request, None ) . expect ( "settings" ) ;
492+ assert_eq ! (
493+ serde_json:: from_str:: <serde_json:: Value >( & settings) . expect( "json" ) ,
494+ json!( { "mcpServers" : {
495+ "docs" : {
496+ "command" : "npx" ,
497+ "args" : [ "-y" , "docs-mcp" ] ,
498+ "env" : { "TOKEN" : "t" }
499+ } ,
500+ "linear" : { "type" : "http" , "httpUrl" : "https://mcp.linear.app/mcp" }
501+ } } ) ,
502+ "http entries must carry httpUrl (gemini reads url as SSE)" ,
503+ ) ;
504+ }
505+
506+ #[ test]
507+ fn sse_entries_keep_url_untranslated ( ) {
508+ let servers = json ! ( {
509+ "linear" : {
510+ "type" : "sse" ,
511+ "url" : "https://mcp.linear.app/sse" ,
512+ "headers" : { "Authorization" : "Bearer x" }
513+ }
514+ } ) ;
471515 let request = request_with_mcp_servers ( Some ( servers. clone ( ) ) ) ;
472516 let settings = gemini_settings_with_mcp_servers ( & request, None ) . expect ( "settings" ) ;
473517 assert_eq ! (
0 commit comments