Skip to content

failed to reconnect: Not Found after initalize in stateless HTTP POST server #393

@jpmcb

Description

@jpmcb

I have an MCP server that only servers JSON over HTTP POST in a stateless fashion. It works like so:

❯ curl http://localhost:3000/mcp \
      -X POST \
      -H 'accept: application/json, text/event-stream' \
      -H 'content-type: application/json' \
       -d '{
    "jsonrpc": "2.0",
    "id": "1",
    "method": "ping"
  }' -vvv
Note: Unnecessary use of -X or --request, POST is already inferred.
* Host localhost:3000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:3000...
* Connected to localhost (::1) port 3000
> POST /mcp HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.7.1
> accept: application/json, text/event-stream
> content-type: application/json
> Content-Length: 55
>
* upload completely sent off: 55 bytes
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 38
< Date: Tue, 02 Sep 2025 19:49:45 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
* Connection #0 to host localhost left intact
{"jsonrpc":"2.0","id":"1","result":{}}

But when attempting to us a very simple go-sdk with this like so:

package main

import (
	"context"
	"log"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

func main() {
	ctx := context.Background()

	// Create a new client, with no features.
	client := mcp.NewClient(&mcp.Implementation{Name: "mcp-client", Version: "v1.0.0"}, nil)

	// Connect to a server over stdin/stdout
	transport := &mcp.StreamableClientTransport{Endpoint: "http://localhost:3000/mcp"}
	session, err := client.Connect(ctx, transport, nil)
	if err != nil {
		log.Fatal(err)
	}
	defer session.Close()

	err = session.Ping(ctx, nil)
	if err != nil {
		log.Fatalf("could not ping: %v", err)
	}
}

I get this error:

❯ go run main.go
2025/09/02 15:54:35 could not ping: calling "ping": failed to reconnect: Not Found
exit status 1

It seems that this client successfully connects and initializes (as I see a log in my server that Received notification: notifications/initialized) but does not reuse the connection:

go-sdk/mcp/streamable.go

Lines 1309 to 1313 in 07b65d7

if resp.StatusCode < 200 || resp.StatusCode >= 300 {
resp.Body.Close()
c.fail(fmt.Errorf("failed to reconnect: %v", http.StatusText(resp.StatusCode)))
return
}

Since the error message is Not found, I'm wondering if my server is returning a 404 for some reason.

But, a very similar program works fine with mark3labs/mcp-go:

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/mark3labs/mcp-go/client"
	"github.com/mark3labs/mcp-go/client/transport"
	"github.com/mark3labs/mcp-go/mcp"
)

func main() {
	ctx := context.Background()

	// Create HTTP transport directly
	httpTransport, err := transport.NewStreamableHTTP(
		"http://localhost:3000/mcp",
	)
	if err != nil {
		log.Fatalf("Failed to create HTTP transport: %v", err)
	}
	defer httpTransport.Close()

	// Create client with sampling support
	mcpClient := client.NewClient(
		httpTransport,
	)

	err = mcpClient.Start(ctx)
	if err != nil {
		log.Fatalf("Failed to start client: %v", err)
	}

	// Initialize the MCP session
	initRequest := mcp.InitializeRequest{
		Params: mcp.InitializeParams{
			ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
			Capabilities:    mcp.ClientCapabilities{},
			ClientInfo: mcp.Implementation{
				Name:    "http-client",
				Version: "0.0.1",
			},
		},
	}

	_, err = mcpClient.Initialize(ctx, initRequest)
	if err != nil {
		log.Fatalf("Failed to initialize MCP session: %v", err)
	}

	toolsResult, err := mcpClient.ListTools(ctx, mcp.ListToolsRequest{})
	if err != nil {
		log.Fatalf("Failed to list tools: %v", err)
	}

	for i, tool := range toolsResult.Tools {
		fmt.Printf("  %d. %s - %s\n", i+1, tool.Name, tool.Description)
	}
}
❯ go run main.go
  1. add - Adds two numbers together and returns the result.
  2. subtract - Subtracts the second number from the first and returns the result.
  3. multiply - Multiplies two numbers together and returns the result.
  4. divide - Divides the first number by the second and returns the result.
  5. power - Raises the first number to the power of the second number.
  6. sqrt - Calculates the square root of the given number.
  7. modulo - Returns the remainder of dividing the first number by the second.
  8. factorial - Calculates the factorial of the given integer.

This also works fine with inspector:

Image

I'm at abit of a loss as to what is going on here. My main questions are:

  1. Does go-sdk support new HTTP connections when the server closes the connection? I.e., can it churn the connection to do a new method after initialization?
  2. What steps can I take to debug this further? For the curious, this is the server I'm using: https://github.com/zuplo/mcp/tree/main/examples/calculator
  3. What is the code path that a "Not found" would be surfaced here? Very curious!

Metadata

Metadata

Assignees

Labels

documentation/errorsImprovements or additions to documentation or error messages.

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions