@@ -73,28 +73,54 @@ func (b *StdioBridge) loop(ctx context.Context) {
7373
7474// Header include Mcp-Session-Id if needed
7575func (b * StdioBridge ) detectTransport (ctx context.Context ) (string , error ) {
76- body , err := buildJSONRPCInitializeRequest ()
76+ // 1) Probe for legacy SSE without sending any JSON-RPC
77+ getReq , err := http .NewRequestWithContext (ctx , "GET" , b .baseURL .String (), nil )
7778 if err != nil {
78- logger .Errorf ("failed to marshal initialization request: %v" , err )
7979 return "" , err
8080 }
81- req , err := http .NewRequestWithContext (ctx , "POST" , b .baseURL .String (), bytes .NewReader (body ))
81+ getReq .Header .Set ("Accept" , "text/event-stream" )
82+ copyHeaders (getReq .Header , b .headers )
83+
84+ getResp , err := http .DefaultClient .Do (getReq )
85+ if err == nil {
86+ ct := getResp .Header .Get ("Content-Type" )
87+ err = getResp .Body .Close () // we’re only peeking at headers here
88+ if err != nil {
89+ return "" , fmt .Errorf ("failed to close GET response body: %w" , err )
90+ }
91+ if strings .HasPrefix (ct , "text/event-stream" ) {
92+ // Legacy SSE: runLegacyReader will open the real stream.
93+ return "legacy-sse" , nil
94+ }
95+ }
96+
97+ // 2) Not SSE -> treat as streamable HTTP and do a proper initialize
98+ body , err := buildJSONRPCInitializeRequest () // keep if your server expects initialize for streamable
8299 if err != nil {
83- logger .Errorf ("failed to create HTTP request: %v" , err )
84100 return "" , err
85101 }
86- req .Header .Set ("Content-Type" , "application/json" )
87- req .Header .Set ("Accept" , "application/json, text/event-stream" )
88- copyHeaders (req .Header , b .headers )
89- resp , err := http .DefaultClient .Do (req )
102+ postReq , err := http .NewRequestWithContext (ctx , "POST" , b .baseURL .String (), bytes .NewReader (body ))
90103 if err != nil {
91104 return "" , err
92105 }
93- defer resp .Body .Close ()
94- if resp .StatusCode >= 400 && resp .StatusCode < 500 {
106+ postReq .Header .Set ("Content-Type" , "application/json" )
107+ postReq .Header .Set ("Accept" , "application/json, text/event-stream" )
108+ copyHeaders (postReq .Header , b .headers )
109+
110+ postResp , err := http .DefaultClient .Do (postReq )
111+ if err != nil {
112+ return "" , err
113+ }
114+ defer postResp .Body .Close ()
115+
116+ // If a streamable server returns info + Mcp-Session-Id, great.
117+ // Some legacy gateways might reply 4xx to POST at base URL.
118+ if postResp .StatusCode >= 400 && postResp .StatusCode < 500 {
95119 return "legacy-sse" , nil
96120 }
97- b .handleInitializeResponse (resp )
121+
122+ // Streamable: capture session headers and emit the response payload.
123+ b .handleInitializeResponse (postResp )
98124 return "streamable-http" , nil
99125}
100126
@@ -104,7 +130,6 @@ func (b *StdioBridge) handleInitializeResponse(resp *http.Response) {
104130 b .headers = make (http.Header )
105131 }
106132 b .headers .Set ("Mcp-Session-Id" , sid )
107- logger .Infof ("Streamable HTTP session ID: %s" , sid )
108133 }
109134 data , err := io .ReadAll (resp .Body )
110135 if err != nil {
@@ -307,15 +332,28 @@ func (b *StdioBridge) updatePostURL(path string) {
307332 return
308333 }
309334 b .postURL = u
310- logger .Infof ("POST URL updated to %s" , b .postURL )
311335}
312336
313337func buildJSONRPCInitializeRequest () ([]byte , error ) {
314338 req := map [string ]interface {}{
315339 "jsonrpc" : "2.0" ,
316340 "id" : 1 ,
317341 "method" : "initialize" ,
318- "params" : map [string ]interface {}{},
342+ "params" : map [string ]interface {}{
343+ "protocolVersion" : "2024-02-01" ,
344+ "capabilities" : map [string ]interface {}{
345+ "prompts" : true ,
346+ "tools" : true ,
347+ "resources" : map [string ]interface {}{
348+ "subscribe" : true ,
349+ "unsubscribe" : true ,
350+ },
351+ },
352+ "clientInfo" : map [string ]interface {}{
353+ "name" : "toolhive-stdio-bridge" ,
354+ "version" : "0.1.0" ,
355+ },
356+ },
319357 }
320358 return json .Marshal (req )
321359}
0 commit comments