@@ -235,6 +235,12 @@ pub enum ExtensionConfig {
235235 // NOTE: set timeout to be optional for compatibility.
236236 // However, new configurations should include this field.
237237 timeout : Option < u64 > ,
238+ /// Optional Unix domain socket path for HTTP-over-UDS transport.
239+ /// When set, the HTTP connection is routed through this socket while
240+ /// `uri` is used for the Host header and path.
241+ /// Use `@name` for Linux abstract sockets.
242+ #[ serde( default ) ]
243+ socket : Option < String > ,
238244 #[ serde( default ) ]
239245 bundled : Option < bool > ,
240246 #[ serde( default ) ]
@@ -307,6 +313,7 @@ impl ExtensionConfig {
307313 headers : HashMap :: new ( ) ,
308314 description : description. into ( ) ,
309315 timeout : Some ( timeout. into ( ) ) ,
316+ socket : None ,
310317 bundled : None ,
311318 available_tools : Vec :: new ( ) ,
312319 }
@@ -460,6 +467,7 @@ impl ExtensionConfig {
460467 env_keys,
461468 headers,
462469 timeout,
470+ socket,
463471 bundled,
464472 available_tools,
465473 } => {
@@ -479,6 +487,7 @@ impl ExtensionConfig {
479487 env_keys : vec ! [ ] ,
480488 headers,
481489 timeout,
490+ socket,
482491 bundled,
483492 available_tools,
484493 } )
@@ -494,8 +503,17 @@ impl std::fmt::Display for ExtensionConfig {
494503 ExtensionConfig :: Sse { name, .. } => {
495504 write ! ( f, "SSE({}: unsupported)" , name)
496505 }
497- ExtensionConfig :: StreamableHttp { name, uri, .. } => {
498- write ! ( f, "StreamableHttp({}: {})" , name, uri)
506+ ExtensionConfig :: StreamableHttp {
507+ name,
508+ uri,
509+ socket,
510+ ..
511+ } => {
512+ if let Some ( socket) = socket {
513+ write ! ( f, "StreamableHttp({}: {} via {})" , name, uri, socket)
514+ } else {
515+ write ! ( f, "StreamableHttp({}: {})" , name, uri)
516+ }
499517 }
500518 ExtensionConfig :: Stdio {
501519 name, cmd, args, ..
@@ -679,6 +697,7 @@ available_tools: []
679697 . into_iter( )
680698 . collect( ) ,
681699 timeout: None ,
700+ socket: None ,
682701 bundled: None ,
683702 available_tools: vec![ ] ,
684703 } ,
@@ -699,6 +718,7 @@ available_tools: []
699718 . into_iter( )
700719 . collect( ) ,
701720 timeout: None ,
721+ socket: None ,
702722 bundled: None ,
703723 available_tools: vec![ ] ,
704724 }
@@ -772,6 +792,7 @@ available_tools: []
772792 . into_iter( )
773793 . collect( ) ,
774794 timeout: None ,
795+ socket: None ,
775796 bundled: None ,
776797 available_tools: vec![ ] ,
777798 } ,
@@ -789,6 +810,7 @@ available_tools: []
789810 . into_iter( )
790811 . collect( ) ,
791812 timeout: None ,
813+ socket: None ,
792814 bundled: None ,
793815 available_tools: vec![ ] ,
794816 }
@@ -803,6 +825,7 @@ available_tools: []
803825 env_keys: vec![ "MY_SECRET" . into( ) ] ,
804826 headers: std:: collections:: HashMap :: new( ) ,
805827 timeout: None ,
828+ socket: None ,
806829 bundled: None ,
807830 available_tools: vec![ ] ,
808831 } ,
@@ -818,6 +841,7 @@ available_tools: []
818841 env_keys: vec![ ] ,
819842 headers: std:: collections:: HashMap :: new( ) ,
820843 timeout: None ,
844+ socket: None ,
821845 bundled: None ,
822846 available_tools: vec![ ] ,
823847 }
@@ -867,4 +891,77 @@ available_tools: []
867891 cfg. set ( "MY_SECRET" , & "secret_value" , true ) . unwrap ( ) ;
868892 assert_eq ! ( config. resolve( & cfg) . await . unwrap( ) , expected) ;
869893 }
894+
895+ #[ test]
896+ fn test_deserialize_streamable_http_with_socket ( ) {
897+ let json = r#"{
898+ "type": "streamable_http",
899+ "name": "test",
900+ "uri": "http://localhost:8080/mcp",
901+ "socket": "@egress.sock"
902+ }"# ;
903+ let config: ExtensionConfig = serde_json:: from_str ( json) . unwrap ( ) ;
904+ match config {
905+ ExtensionConfig :: StreamableHttp { socket, .. } => {
906+ assert_eq ! ( socket, Some ( "@egress.sock" . to_string( ) ) ) ;
907+ }
908+ other => panic ! ( "expected StreamableHttp, got {:?}" , other) ,
909+ }
910+ }
911+
912+ #[ test]
913+ fn test_deserialize_streamable_http_without_socket ( ) {
914+ let json = r#"{
915+ "type": "streamable_http",
916+ "name": "test",
917+ "uri": "http://localhost:8080/mcp"
918+ }"# ;
919+ let config: ExtensionConfig = serde_json:: from_str ( json) . unwrap ( ) ;
920+ match config {
921+ ExtensionConfig :: StreamableHttp { socket, .. } => {
922+ assert_eq ! ( socket, None ) ;
923+ }
924+ other => panic ! ( "expected StreamableHttp, got {:?}" , other) ,
925+ }
926+ }
927+
928+ #[ test]
929+ fn test_display_streamable_http_with_socket ( ) {
930+ let config = ExtensionConfig :: StreamableHttp {
931+ name : "test" . into ( ) ,
932+ description : String :: new ( ) ,
933+ uri : "http://localhost:8080/mcp" . into ( ) ,
934+ envs : extension:: Envs :: default ( ) ,
935+ env_keys : vec ! [ ] ,
936+ headers : std:: collections:: HashMap :: new ( ) ,
937+ timeout : None ,
938+ socket : Some ( "@egress.sock" . to_string ( ) ) ,
939+ bundled : None ,
940+ available_tools : vec ! [ ] ,
941+ } ;
942+ assert_eq ! (
943+ config. to_string( ) ,
944+ "StreamableHttp(test: http://localhost:8080/mcp via @egress.sock)"
945+ ) ;
946+ }
947+
948+ #[ test]
949+ fn test_display_streamable_http_without_socket ( ) {
950+ let config = ExtensionConfig :: StreamableHttp {
951+ name : "test" . into ( ) ,
952+ description : String :: new ( ) ,
953+ uri : "http://localhost:8080/mcp" . into ( ) ,
954+ envs : extension:: Envs :: default ( ) ,
955+ env_keys : vec ! [ ] ,
956+ headers : std:: collections:: HashMap :: new ( ) ,
957+ timeout : None ,
958+ socket : None ,
959+ bundled : None ,
960+ available_tools : vec ! [ ] ,
961+ } ;
962+ assert_eq ! (
963+ config. to_string( ) ,
964+ "StreamableHttp(test: http://localhost:8080/mcp)"
965+ ) ;
966+ }
870967}
0 commit comments