Skip to content

No tools are advertised when assigning a pointer type in the generic for CallToolResultFor #200

@sethvargo

Description

@sethvargo

Describe the bug
When the generic type for CallToolResultFor is a pointer in the ToolHandlerFor, the tool is not included in clients.

Image

This is likely due to the response schema for tools/list being invalid.

To Reproduce

Prove that tools are included in the response with non-pointer types:

  1. Create a small server:

    package main
    
    import (
    	"context"
    	"net/http"
    
    	"github.com/modelcontextprotocol/go-sdk/mcp"
    )
    
    func main() {
    	server := mcp.NewServer(&mcp.Implementation{Name: "hello"}, nil)
    
    	mcp.AddTool(server, &mcp.Tool{
    		Name:        "greet",
    		Description: "Say hello",
    	}, func(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolParamsFor[struct{}]) (*mcp.CallToolResultFor[struct{}], error) {
    		return &mcp.CallToolResultFor[struct{}]{
    			Content: []mcp.Content{
    				&mcp.TextContent{Text: "hello"},
    			},
    		}, nil
    	})
    
    	http.ListenAndServe(":8080", mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server {
    		return server
    	}, nil))
    }
  2. Start the server:

    go run main.go
  3. List the tools:

    SESSION_ID=$(curl -sfo /dev/null -w '%header{mcp-session-id}' localhost:8080 \
      -H 'Accept: application/json,text/event-stream' \
      -H 'Mcp-Protocol-Version: 2025-06-18' \
      -d '{
      "jsonrpc":"2.0",
      "id":1,
      "method":"initialize",
      "params":{
        "protocolVersion":"2025-06-18",
        "capabilities":{},
        "clientInfo":{"name":"Seth", "title":"Seth Test Client", "version":"1.0.0"}
      }
    }'
    )
    curl -v localhost:8080 \
      -H "Accept: application/json,text/event-stream" \
      -H "Mcp-Protocol-Version: 2025-06-18" \
      -H "Mcp-Session-Id: ${SESSION_ID}" \
      -d '{
      "jsonrpc":"2.0",
      "method":"notifications/initialized"
    }'
    curl -v localhost:8080 \
      -H 'Accept: application/json,text/event-stream' \
      -H "Mcp-Protocol-Version: 2025-06-18" \
      -H "Mcp-Session-Id: ${SESSION_ID}" \
      -d '{
      "jsonrpc":"2.0",
      "id":1,
      "method":"tools/list",
      "params":{}
    }'
  4. Get a response:

    event: message
    id: 3_0
    data: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"description":"Say hello","inputSchema":{"type":"object","additionalProperties":{"not":{}}},"name":"greet","outputSchema":{"type":"object","additionalProperties":{"not":{}}}}]}}
    
  5. Stop the server

  6. Alter the code to use a pointer in mcp.CallToolResultFor:

    -	}, func(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolParamsFor[struct{}]) (*mcp.CallToolResultFor[struct{}], error) {
    +	}, func(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolParamsFor[struct{}]) (*mcp.CallToolResultFor[*struct{}], error) {
  7. Start the server:

    go run main.go
  8. Run the same commands from step 3 above:

    SESSION_ID=$(curl -sfo /dev/null -w '%header{mcp-session-id}' localhost:8080 \
      -H 'Accept: application/json,text/event-stream' \
      -H 'Mcp-Protocol-Version: 2025-06-18' \
      -d '{
      "jsonrpc":"2.0",
      "id":1,
      "method":"initialize",
      "params":{
        "protocolVersion":"2025-06-18",
        "capabilities":{},
        "clientInfo":{"name":"Seth", "title":"Seth Test Client", "version":"1.0.0"}
      }
    }'
    )
    curl -v localhost:8080 \
      -H "Accept: application/json,text/event-stream" \
      -H "Mcp-Protocol-Version: 2025-06-18" \
      -H "Mcp-Session-Id: ${SESSION_ID}" \
      -d '{
      "jsonrpc":"2.0",
      "method":"notifications/initialized"
    }'
    curl -v localhost:8080 \
      -H 'Accept: application/json,text/event-stream' \
      -H "Mcp-Protocol-Version: 2025-06-18" \
      -H "Mcp-Session-Id: ${SESSION_ID}" \
      -d '{
      "jsonrpc":"2.0",
      "id":1,
      "method":"tools/list",
      "params":{}
    }'
  9. Get a response:

    event: message
    id: 3_0
    data: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"description":"Say hello","inputSchema":{"type":"object","additionalProperties":{"not":{}}},"name":"greet","outputSchema":{"type":["null","object"],"additionalProperties":{"not":{}}}}]}}
    

Expected behavior
I expect pointer types to be permitted. Unfortunately the response from the pointer-types is invalid and not recognized by Gemini CLI or Claude Code Desktop (I did not try other clients, but I expect it's the same). Diff from the resposnes:

// concrete type
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "description": "Say hello",
        "inputSchema": {
          "type": "object",
          "additionalProperties": {
            "not": {}
          }
        },
        "name": "greet",
        "outputSchema": {
          "type": "object",
          "additionalProperties": {
            "not": {}
          }
        }
      }
    ]
  }
}
// pointer
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "description": "Say hello",
        "inputSchema": {
          "type": "object",
          "additionalProperties": {
            "not": {}
          }
        },
        "name": "greet",
        "outputSchema": {
          "type": [
            "null",
            "object"
          ],
          "additionalProperties": {
            "not": {}
          }
        }
      }
    ]
  }
}

Logs
N/A

Additional context

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingrelease blockerThis issue blocks the release milestone with which it is associated.

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions