-
Notifications
You must be signed in to change notification settings - Fork 723
Description
Description
The streamableHttpSession struct does not implement the GetClientCapabilities() method from the SessionWithClientInfo interface, making it impossible for servers to check if a client supports sampling (or other capabilities) before attempting to use those features.
When using StreamableHTTP transport with stateful sessions (WithStateLess(false)), the session is stored and reused across requests, but the client capabilities that were declared during initialization are not accessible. Type assertion to SessionWithClientInfo fails because the required methods are not implemented on streamableHttpSession.
Expected: streamableHttpSession should implement the complete SessionWithClientInfo interface, including GetClientCapabilities() and SetClientCapabilities(), just like sseSession does.
Actual: Type assertion fails and there is no way to check client capabilities before calling methods like RequestSampling().
Code Sample
import (
"context"
"fmt"
"log/slog"
"reflect"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
// Server setup with stateful StreamableHTTP
func StartServer() {
mcpServer := server.NewMCPServer("example", "1.0.0")
httpServer := server.NewStreamableHTTPServer(
mcpServer,
server.WithStateLess(false),
)
httpServer.Start(":8082")
}
// In a tool handler, trying to check if client supports sampling
func checkSamplingSupport(ctx context.Context) (bool, error) {
session := server.ClientSessionFromContext(ctx)
if session == nil {
return false, fmt.Errorf("no client session found in context")
}
slog.Info("Checking session type",
"session_id", session.SessionID(),
"session_type", fmt.Sprintf("%T", session),
)
// Try direct type assertion - this fails
type ClientCapabilitiesGetter interface {
GetClientCapabilities() mcp.ClientCapabilities
}
if clientSession, ok := session.(ClientCapabilitiesGetter); ok {
clientCapabilities := clientSession.GetClientCapabilities()
slog.Info("Got client capabilities via direct interface",
"has_sampling", clientCapabilities.Sampling != nil,
)
return clientCapabilities.Sampling != nil, nil
}
// Use reflection to see what's actually available
sessionType := reflect.TypeOf(session)
slog.Info("Session reflection info",
"type", sessionType.String(),
"num_methods", sessionType.NumMethod(),
)
for i := 0; i < sessionType.NumMethod(); i++ {
method := sessionType.Method(i)
slog.Info("Available method", "name", method.Name, "type", method.Type.String())
}
slog.Warn("Client session does not implement SessionWithClientInfo",
"session_type", fmt.Sprintf("%T", session),
"session_id", session.SessionID(),
)
return false, fmt.Errorf("session does not support GetClientCapabilities")
}Logs or Error Messages
{"level":"INFO","msg":"Checking session type","session_id":"mcp-session-38dd29cf","session_type":"*server.streamableHttpSession"}
{"level":"INFO","msg":"Session reflection info","type":"*server.streamableHttpSession","num_methods":16}
{"level":"INFO","msg":"Available method","name":"GetLogLevel"}
{"level":"INFO","msg":"Available method","name":"GetSessionResourceTemplates"}
{"level":"INFO","msg":"Available method","name":"GetSessionResources"}
{"level":"INFO","msg":"Available method","name":"GetSessionTools"}
{"level":"INFO","msg":"Available method","name":"Initialize"}
{"level":"INFO","msg":"Available method","name":"Initialized"}
{"level":"INFO","msg":"Available method","name":"ListRoots"}
{"level":"INFO","msg":"Available method","name":"NotificationChannel"}
{"level":"INFO","msg":"Available method","name":"RequestElicitation"}
{"level":"INFO","msg":"Available method","name":"RequestSampling"}
{"level":"INFO","msg":"Available method","name":"SessionID"}
{"level":"INFO","msg":"Available method","name":"SetLogLevel"}
{"level":"INFO","msg":"Available method","name":"SetSessionResourceTemplates"}
{"level":"INFO","msg":"Available method","name":"SetSessionResources"}
{"level":"INFO","msg":"Available method","name":"SetSessionTools"}
{"level":"INFO","msg":"Available method","name":"UpgradeToSSEWhenReceiveNotification"}
{"level":"WARN","msg":"Client session does not implement SessionWithClientInfo"}
Note: GetClientCapabilities and SetClientCapabilities are missing
Environment
- Go version: 1.24.9
- mcp-go version: v0.43.0
- OS: macOS (also affects Linux)
- Transport: StreamableHTTP with stateful mode
Additional Context
The SessionWithClientInfo interface was added in v0.39.1 and includes these methods:
type SessionWithClientInfo interface {
ClientSession
GetClientInfo() mcp.Implementation
SetClientInfo(clientInfo mcp.Implementation)
GetClientCapabilities() mcp.ClientCapabilities
SetClientCapabilities(clientCapabilities mcp.ClientCapabilities)
}The sseSession struct in server/sse.go correctly implements all these methods, but streamableHttpSession does not. This creates inconsistency between transports and prevents proper capability checking before operations like sampling.
Current workaround is to blindly call RequestSampling() and handle errors.
Possible Solution
Add GetClientInfo(), SetClientInfo(), GetClientCapabilities(), and SetClientCapabilities() methods to streamableHttpSession in server/streamable_http.go, following the same implementation pattern as sseSession in server/sse.go.