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

documentationImprovements 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