Repository: metoro-io/mcp-golang

Issue:
[BUG] http example not work as normally
The client program will keep blocking

After a brief check of the source code, I found that HTTPTransport and baseTransport have the same members, and when calling HTTPTransport.SetMessageHandler, HTTPTransport.messageHandler is set instead of HTTPTransport.(*baseTransport).messageHandler, which results in the actual messageHandler being null, causing the server to have no response and causing the client to be blocked.

Discussion:
@a67793581: #76 @rehacktive: why this is still happening? why the bug has not been fixed even if the PR solves this?@a67793581: > why this is still happening? why the bug has not been fixed even if the PR solves this?

Not merged yet, you need to use the exclusive code of this pr@ecekyn: Apologies for the delay here, #76 has been merged: https://github.com/metoro-io/mcp-golang/releases/tag/v0.11.0

Solution patch:
diff --git a/client.go b/client.go
index 672e217..bb650df 100644
--- a/client.go
+++ b/client.go
@@ -15,6 +15,7 @@ type Client struct {
 	protocol     *protocol.Protocol
 	capabilities *ServerCapabilities
 	initialized  bool
+	info         ClientInfo
 }
 
 // NewClient creates a new MCP client with the specified transport
@@ -25,6 +26,20 @@ func NewClient(transport transport.Transport) *Client {
 	}
 }
 
+type ClientInfo struct {
+	Name    string `json:"name"`
+	Version string `json:"version"`
+}
+
+// NewClientWithInfo create a new client with info. This is required by anthorpic mcp tools
+func NewClientWithInfo(transport transport.Transport, info ClientInfo) *Client {
+	return &Client{
+		transport: transport,
+		protocol:  protocol.NewProtocol(nil),
+		info:      info,
+	}
+}
+
 // Initialize connects to the server and retrieves its capabilities
 func (c *Client) Initialize(ctx context.Context) (*InitializeResponse, error) {
 	if c.initialized {
@@ -37,7 +52,11 @@ func (c *Client) Initialize(ctx context.Context) (*InitializeResponse, error) {
 	}
 
 	// Make initialize request to server
-	response, err := c.protocol.Request(ctx, "initialize", map[string]interface{}{}, nil)
+	response, err := c.protocol.Request(ctx, "initialize", map[string]interface{}{
+		"protocolVersion": "1.0",
+		"capabilities":    map[string]interface{}{},
+		"clientInfo":      c.info,
+	}, nil)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to initialize")
 	}
diff --git a/resource_response_types.go b/resource_response_types.go
index 5b16e27..2dd5e8d 100644
--- a/resource_response_types.go
+++ b/resource_response_types.go
@@ -36,3 +36,29 @@ type ResourceSchema struct {
 	// The URI of this resource.
 	Uri string `json:"uri" yaml:"uri" mapstructure:"uri"`
 }
+
+// A resource template that defines a pattern for dynamic resources.
+type ResourceTemplateSchema struct {
+	// Annotations corresponds to the JSON schema field "annotations".
+	Annotations *Annotations `json:"annotations,omitempty" yaml:"annotations,omitempty" mapstructure:"annotations,omitempty"`
+
+	// A description of what resources matching this template represent.
+	Description *string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description,omitempty"`
+
+	// The MIME type of resources matching this template, if known.
+	MimeType *string `json:"mimeType,omitempty" yaml:"mimeType,omitempty" mapstructure:"mimeType,omitempty"`
+
+	// A human-readable name for this template.
+	Name string `json:"name" yaml:"name" mapstructure:"name"`
+
+	// The URI template following RFC 6570.
+	UriTemplate string `json:"uriTemplate" yaml:"uriTemplate" mapstructure:"uriTemplate"`
+}
+
+// The server's response to a resources/templates/list request from the client.
+type ListResourceTemplatesResponse struct {
+	// Templates corresponds to the JSON schema field "templates".
+	Templates []*ResourceTemplateSchema `json:"resourceTemplates" yaml:"resourceTemplates" mapstructure:"resourceTemplates"`
+	// NextCursor is a cursor for pagination. If not nil, there are more templates available.
+	NextCursor *string `json:"nextCursor,omitempty" yaml:"nextCursor,omitempty" mapstructure:"nextCursor,omitempty"`
+}
diff --git a/server.go b/server.go
index 84fb08b..2a656c3 100644
--- a/server.go
+++ b/server.go
@@ -107,6 +107,7 @@ type Server struct {
 	tools              *datastructures.SyncMap[string, *tool]
 	prompts            *datastructures.SyncMap[string, *prompt]
 	resources          *datastructures.SyncMap[string, *resource]
+	resourceTemplates  *datastructures.SyncMap[string, *resourceTemplate]
 	serverInstructions *string
 	serverName         string
 	serverVersion      string
@@ -134,6 +135,13 @@ type resource struct {
 	Handler     func(context.Context) *resourceResponseSent
 }
 
+type resourceTemplate struct {
+	Name        string
+	Description string
+	UriTemplate string
+	MimeType    string
+}
+
 type ServerOptions func(*Server)
 
 func WithProtocol(protocol *protocol.Protocol) ServerOptions {
@@ -163,11 +171,12 @@ func WithVersion(version string) ServerOptions {
 
 func NewServer(transport transport.Transport, options ...ServerOptions) *Server {
 	server := &Server{
-		protocol:  protocol.NewProtocol(nil),
-		transport: transport,
-		tools:     new(datastructures.SyncMap[string, *tool]),
-		prompts:   new(datastructures.SyncMap[string, *prompt]),
-		resources: new(datastructures.SyncMap[string, *resource]),
+		protocol:          protocol.NewProtocol(nil),
+		transport:         transport,
+		tools:             new(datastructures.SyncMap[string, *tool]),
+		prompts:           new(datastructures.SyncMap[string, *prompt]),
+		resources:         new(datastructures.SyncMap[string, *resource]),
+		resourceTemplates: new(datastructures.SyncMap[string, *resourceTemplate]),
 	}
 	for _, option := range options {
 		option(server)
@@ -299,6 +308,26 @@ func validateResourceHandler(handler any) error {
 	return nil
 }
 
+func (s *Server) RegisterResourceTemplate(uriTemplate string, name string, description string, mimeType string) error {
+	s.resourceTemplates.Store(uriTemplate, &resourceTemplate{
+		Name:        name,
+		Description: description,
+		UriTemplate: uriTemplate,
+		MimeType:    mimeType,
+	})
+	return s.sendResourceListChangedNotification()
+}
+
+func (s *Server) CheckResourceTemplateRegistered(uriTemplate string) bool {
+	_, ok := s.resourceTemplates.Load(uriTemplate)
+	return ok
+}
+
+func (s *Server) DeregisterResourceTemplate(uriTemplate string) error {
+	s.resourceTemplates.Delete(uriTemplate)
+	return s.sendResourceListChangedNotification()
+}
+
 func (s *Server) RegisterPrompt(name string, description string, handler any) error {
 	err := validatePromptHandler(handler)
 	if err != nil {
@@ -553,6 +582,7 @@ func (s *Server) Serve() error {
 	pr.SetRequestHandler("prompts/list", s.handleListPrompts)
 	pr.SetRequestHandler("prompts/get", s.handlePromptCalls)
 	pr.SetRequestHandler("resources/list", s.handleListResources)
+	pr.SetRequestHandler("resources/templates/list", s.handleListResourceTemplates)
 	pr.SetRequestHandler("resources/read", s.handleResourceCalls)
 	err := pr.Connect(s.transport)
 	if err != nil {
@@ -829,6 +859,78 @@ func (s *Server) handleListResources(ctx context.Context, request *transport.Bas
 	}, nil
 }
 
+func (s *Server) handleListResourceTemplates(ctx context.Context, request *transport.BaseJSONRPCRequest, extra protocol.RequestHandlerExtra) (transport.JsonRpcBody, error) {
+	type resourceTemplateRequestParams struct {
+		Cursor *string `json:"cursor"`
+	}
+	var params resourceTemplateRequestParams
+	if request.Params == nil {
+		params = resourceTemplateRequestParams{}
+	} else {
+		err := json.Unmarshal(request.Params, &params)
+		if err != nil {
+			return nil, errors.Wrap(err, "failed to unmarshal arguments")
+		}
+	}
+
+	// Order by URI template for pagination
+	var orderedTemplates []*resourceTemplate
+	s.resourceTemplates.Range(func(k string, t *resourceTemplate) bool {
+		orderedTemplates = append(orderedTemplates, t)
+		return true
+	})
+	sort.Slice(orderedTemplates, func(i, j int) bool {
+		return orderedTemplates[i].UriTemplate < orderedTemplates[j].UriTemplate
+	})
+
+	startPosition := 0
+	if params.Cursor != nil {
+		// Base64 decode the cursor
+		c, err := base64.StdEncoding.DecodeString(*params.Cursor)
+		if err != nil {
+			return nil, errors.Wrap(err, "failed to decode cursor")
+		}
+		cString := string(c)
+		// Iterate through the templates until we find an entry > the cursor
+		for i := 0; i < len(orderedTemplates); i++ {
+			if orderedTemplates[i].UriTemplate > cString {
+				startPosition = i
+				break
+			}
+		}
+	}
+	endPosition := len(orderedTemplates)
+	if s.paginationLimit != nil {
+		// Make sure we don't go out of bounds
+		if len(orderedTemplates) > startPosition+*s.paginationLimit {
+			endPosition = startPosition + *s.paginationLimit
+		}
+	}
+
+	templatesToReturn := make([]*ResourceTemplateSchema, 0)
+	for i := startPosition; i < endPosition; i++ {
+		t := orderedTemplates[i]
+		templatesToReturn = append(templatesToReturn, &ResourceTemplateSchema{
+			Annotations: nil,
+			Description: &t.Description,
+			MimeType:    &t.MimeType,
+			Name:        t.Name,
+			UriTemplate: t.UriTemplate,
+		})
+	}
+
+	return ListResourceTemplatesResponse{
+		Templates: templatesToReturn,
+		NextCursor: func() *string {
+			if s.paginationLimit != nil && len(templatesToReturn) >= *s.paginationLimit {
+				toString := base64.StdEncoding.EncodeToString([]byte(templatesToReturn[len(templatesToReturn)-1].UriTemplate))
+				return &toString
+			}
+			return nil
+		}(),
+	}, nil
+}
+
 func (s *Server) handlePromptCalls(ctx context.Context, req *transport.BaseJSONRPCRequest, extra protocol.RequestHandlerExtra) (transport.JsonRpcBody, error) {
 	params := baseGetPromptRequestParamsArguments{}
 	// Instantiate a struct of the type of the arguments
diff --git a/transport/http/http.go b/transport/http/http.go
index 570ae1b..d03ccf0 100644
--- a/transport/http/http.go
+++ b/transport/http/http.go
@@ -20,7 +20,6 @@ type HTTPTransport struct {
 	closeHandler   func()
 	mu             sync.RWMutex
 	addr           string
-	responseMap    map[int64]chan *transport.BaseJsonRpcMessage
 }
 
 // NewHTTPTransport creates a new HTTP transport that listens on the specified endpoint
@@ -29,7 +28,6 @@ func NewHTTPTransport(endpoint string) *HTTPTransport {
 		baseTransport: newBaseTransport(),
 		endpoint:      endpoint,
 		addr:          ":8080", // Default port
-		responseMap:   make(map[int64]chan *transport.BaseJsonRpcMessage),
 	}
 }
 
@@ -55,14 +53,27 @@ func (t *HTTPTransport) Start(ctx context.Context) error {
 // Send implements Transport.Send
 func (t *HTTPTransport) Send(ctx context.Context, message *transport.BaseJsonRpcMessage) error {
 	key := message.JsonRpcResponse.Id
-	responseChannel := t.responseMap[int64(key)]
+	fmt.Printf("[Send] Attempting to send response with key: %d\n", key)
+
+	responseChannel := t.baseTransport.responseMap[int64(key)]
 	if responseChannel == nil {
+		fmt.Printf("[Send] Response map keys: %v\n", t.getResponseMapKeys())
+
 		return fmt.Errorf("no response channel found for key: %d", key)
 	}
 	responseChannel <- message
 	return nil
 }
 
+// Helper method to get keys
+func (t *HTTPTransport) getResponseMapKeys() []int64 {
+	keys := make([]int64, 0, len(t.baseTransport.responseMap))
+	for k := range t.baseTransport.responseMap {
+		keys = append(keys, k)
+	}
+	return keys
+}
+
 // Close implements Transport.Close
 func (t *HTTPTransport) Close() error {
 	if t.server != nil {
@@ -94,6 +105,7 @@ func (t *HTTPTransport) SetErrorHandler(handler func(error)) {
 func (t *HTTPTransport) SetMessageHandler(handler func(ctx context.Context, message *transport.BaseJsonRpcMessage)) {
 	t.mu.Lock()
 	defer t.mu.Unlock()
+	t.baseTransport.messageHandler = handler
 	t.messageHandler = handler
 }
 



Test patch:
diff --git a/server_test.go b/server_test.go
index dffe0d6..851aa06 100644
--- a/server_test.go
+++ b/server_test.go
@@ -572,3 +572,137 @@ func TestHandleListResourcesPagination(t *testing.T) {
 		t.Error("Expected no next cursor when pagination is disabled")
 	}
 }
+
+func TestHandleListResourceTemplatesPagination(t *testing.T) {
+	mockTransport := testingutils.NewMockTransport()
+	server := NewServer(mockTransport)
+	err := server.Serve()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Register templates in a non alphabetical order
+	templateURIs := []string{
+		"b://{param}/resource",
+		"a://{param}/resource",
+		"c://{param}/resource",
+		"e://{param}/resource",
+		"d://{param}/resource",
+	}
+	for _, uri := range templateURIs {
+		err = server.RegisterResourceTemplate(
+			uri,
+			"template-"+uri,
+			"Test template "+uri,
+			"text/plain",
+		)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
+
+	// Set pagination limit to 2 items per page
+	limit := 2
+	server.paginationLimit = &limit
+
+	// Test first page (no cursor)
+	resp, err := server.handleListResourceTemplates(context.Background(), &transport.BaseJSONRPCRequest{
+		Params: []byte(`{}`),
+	}, protocol.RequestHandlerExtra{})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	templatesResp, ok := resp.(ListResourceTemplatesResponse)
+	if !ok {
+		t.Fatal("Expected ListResourceTemplatesResponse")
+	}
+
+	// Verify first page
+	if len(templatesResp.Templates) != 2 {
+		t.Errorf("Expected 2 templates, got %d", len(templatesResp.Templates))
+	}
+	if templatesResp.Templates[0].UriTemplate != "a://{param}/resource" || templatesResp.Templates[1].UriTemplate != "b://{param}/resource" {
+		t.Errorf("Unexpected templates in first page: %v", templatesResp.Templates)
+	}
+	if templatesResp.NextCursor == nil {
+		t.Fatal("Expected next cursor for first page")
+	}
+
+	// Test second page
+	resp, err = server.handleListResourceTemplates(context.Background(), &transport.BaseJSONRPCRequest{
+		Params: []byte(`{"cursor":"` + *templatesResp.NextCursor + `"}`),
+	}, protocol.RequestHandlerExtra{})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	templatesResp, ok = resp.(ListResourceTemplatesResponse)
+	if !ok {
+		t.Fatal("Expected ListResourceTemplatesResponse")
+	}
+
+	// Verify second page
+	if len(templatesResp.Templates) != 2 {
+		t.Errorf("Expected 2 templates, got %d", len(templatesResp.Templates))
+	}
+	if templatesResp.Templates[0].UriTemplate != "c://{param}/resource" || templatesResp.Templates[1].UriTemplate != "d://{param}/resource" {
+		t.Errorf("Unexpected templates in second page: %v", templatesResp.Templates)
+	}
+	if templatesResp.NextCursor == nil {
+		t.Fatal("Expected next cursor for second page")
+	}
+
+	// Test last page
+	resp, err = server.handleListResourceTemplates(context.Background(), &transport.BaseJSONRPCRequest{
+		Params: []byte(`{"cursor":"` + *templatesResp.NextCursor + `"}`),
+	}, protocol.RequestHandlerExtra{})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	templatesResp, ok = resp.(ListResourceTemplatesResponse)
+	if !ok {
+		t.Fatal("Expected ListResourceTemplatesResponse")
+	}
+
+	// Verify last page
+	if len(templatesResp.Templates) != 1 {
+		t.Errorf("Expected 1 template, got %d", len(templatesResp.Templates))
+	}
+	if templatesResp.Templates[0].UriTemplate != "e://{param}/resource" {
+		t.Errorf("Unexpected template in last page: %v", templatesResp.Templates)
+	}
+	if templatesResp.NextCursor != nil {
+		t.Error("Expected no next cursor for last page")
+	}
+
+	// Test invalid cursor
+	_, err = server.handleListResourceTemplates(context.Background(), &transport.BaseJSONRPCRequest{
+		Params: []byte(`{"cursor":"invalid-cursor"}`),
+	}, protocol.RequestHandlerExtra{})
+	if err == nil {
+		t.Error("Expected error for invalid cursor")
+	}
+
+	// Test without pagination (should return all templates)
+	server.paginationLimit = nil
+	resp, err = server.handleListResourceTemplates(context.Background(), &transport.BaseJSONRPCRequest{
+		Params: []byte(`{}`),
+	}, protocol.RequestHandlerExtra{})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	templatesResp, ok = resp.(ListResourceTemplatesResponse)
+	if !ok {
+		t.Fatal("Expected ListResourceTemplatesResponse")
+	}
+
+	if len(templatesResp.Templates) != 5 {
+		t.Errorf("Expected 5 templates, got %d", len(templatesResp.Templates))
+	}
+	if templatesResp.NextCursor != nil {
+		t.Error("Expected no next cursor when pagination is disabled")
+	}
+}