Repository: mark3labs/mcp-go

Issue:
bug: Streamable HTTP transport incorrectly logging error
## Description

When connecting an MCP client to server via Streamable HTTP  under the [2025-06-18 spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports), the transport logs an error if the most recent message cannot be unmarshaled:

https://github.com/mark3labs/mcp-go/blob/38ac77cf9f42386f1beea4f224fa45eb9b08a926/client/transport/streamable_http.go#L399-L402

This behavior is incorrect, as the next response is not required to be a JSONRPC payload per the spec:

> 6. If the server initiates an SSE stream:
> - The SSE stream SHOULD eventually include JSON-RPC response for the JSON-RPC request sent in the POST body.
> - The server MAY send JSON-RPC requests and notifications before sending the JSON-RPC response. These messages SHOULD relate to the originating client request.

Such is the case when connecting to the DeepWiki MCP server, for example, which sends pings on the SSE stream, producing the error log below. This may be helpful information to log, but it shouldn't be logged as an error, and more detailed information should be logged alongside it.

## Logs or Error Messages

```text
2025/07/24 14:31:15 ERROR: failed to unmarshal message: invalid character 'p' looking for beginning of value
```

## Environment

- Go version (see go.mod): 1.23.10 (project requirement), actual installed: go1.24.4
- mcp-go version (see go.mod): 0.33.0
- Other relevant environment details:
    - OS: macOS (darwin 24.5.0)
    - Architecture: arm64 (Apple Silicon)

## Possible Solution

Make debug level log, include more information.


Hints from issue comments:

Solution patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 6708643..523417b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -12,6 +12,7 @@ jobs:
         uses: actions/checkout@v3
 
       - name: Create GitHub Release
+        id: create_release
         uses: actions/create-release@v1
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -20,3 +21,38 @@ jobs:
           release_name: Release ${{ github.ref }}
           draft: false
           prerelease: false
+
+      - name: Send Discord Notification
+        if: success()
+        env:
+          DISCORD_WEBHOOK: ${{ secrets.RELEASES_WEBHOOK }}
+          TAG_NAME: ${{ github.ref_name }}
+          RELEASE_URL: ${{ steps.create_release.outputs.html_url }}
+        run: |
+          curl -H "Content-Type: application/json" \
+            -X POST \
+            -d "{
+              \"embeds\": [{
+                \"title\": \"🚀 New Release: $TAG_NAME\",
+                \"description\": \"A new version of mcp-go has been released!\",
+                \"color\": 5814783,
+                \"fields\": [
+                  {
+                    \"name\": \"Version\",
+                    \"value\": \"$TAG_NAME\",
+                    \"inline\": true
+                  },
+                  {
+                    \"name\": \"Repository\",
+                    \"value\": \"[mcp-go](https://github.com/${{ github.repository }})\",
+                    \"inline\": true
+                  }
+                ],
+                \"footer\": {
+                  \"text\": \"Released via GitHub Actions\"
+                },
+                \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\",
+                \"url\": \"$RELEASE_URL\"
+              }]
+            }" \
+            $DISCORD_WEBHOOK
diff --git a/README.md b/README.md
index 6ddc03e..f047c3f 100644
--- a/README.md
+++ b/README.md
@@ -537,7 +537,7 @@ For examples, see the [`examples/`](examples/) directory.
 
 ### Transports
 
-MCP-Go supports stdio, SSE and streamable-HTTP transport layers.
+MCP-Go supports stdio, SSE and streamable-HTTP transport layers. For SSE transport, you can use `SetConnectionLostHandler()` to detect and handle HTTP/2 idle timeout disconnections (NO_ERROR) for implementing reconnection logic.
 
 ### Session Management
 
diff --git a/client/client.go b/client/client.go
index 6990ae1..0d47fcb 100644
--- a/client/client.go
+++ b/client/client.go
@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"slices"
 	"sync"
 	"sync/atomic"
 
@@ -22,6 +23,7 @@ type Client struct {
 	requestID          atomic.Int64
 	clientCapabilities mcp.ClientCapabilities
 	serverCapabilities mcp.ServerCapabilities
+	protocolVersion    string
 	samplingHandler    SamplingHandler
 }
 
@@ -75,9 +77,16 @@ func (c *Client) Start(ctx context.Context) error {
 	if c.transport == nil {
 		return fmt.Errorf("transport is nil")
 	}
-	err := c.transport.Start(ctx)
-	if err != nil {
-		return err
+
+	if _, ok := c.transport.(*transport.Stdio); !ok {
+		// the stdio transport from NewStdioMCPClientWithOptions
+		// is already started, dont start again.
+		//
+		// Start the transport for other transport types
+		err := c.transport.Start(ctx)
+		if err != nil {
+			return err
+		}
 	}
 
 	c.transport.SetNotificationHandler(func(notification mcp.JSONRPCNotification) {
@@ -111,6 +120,17 @@ func (c *Client) OnNotification(
 	c.notifications = append(c.notifications, handler)
 }
 
+// OnConnectionLost registers a handler function to be called when the connection is lost.
+// This is useful for handling HTTP2 idle timeout disconnections that should not be treated as errors.
+func (c *Client) OnConnectionLost(handler func(error)) {
+	type connectionLostSetter interface {
+		SetConnectionLostHandler(func(error))
+	}
+	if setter, ok := c.transport.(connectionLostSetter); ok {
+		setter.SetConnectionLostHandler(handler)
+	}
+}
+
 // sendRequest sends a JSON-RPC request to the server and waits for a response.
 // Returns the raw JSON response message or an error if the request fails.
 func (c *Client) sendRequest(
@@ -176,8 +196,19 @@ func (c *Client) Initialize(
 		return nil, fmt.Errorf("failed to unmarshal response: %w", err)
 	}
 
-	// Store serverCapabilities
+	// Validate protocol version
+	if !slices.Contains(mcp.ValidProtocolVersions, result.ProtocolVersion) {
+		return nil, mcp.UnsupportedProtocolVersionError{Version: result.ProtocolVersion}
+	}
+
+	// Store serverCapabilities and protocol version
 	c.serverCapabilities = result.Capabilities
+	c.protocolVersion = result.ProtocolVersion
+
+	// Set protocol version on HTTP transports
+	if httpConn, ok := c.transport.(transport.HTTPConnection); ok {
+		httpConn.SetProtocolVersion(result.ProtocolVersion)
+	}
 
 	// Send initialized notification
 	notification := mcp.JSONRPCNotification{
diff --git a/client/sse.go b/client/sse.go
index ae2ebca..07512a9 100644
--- a/client/sse.go
+++ b/client/sse.go
@@ -23,12 +23,10 @@ func WithHTTPClient(httpClient *http.Client) transport.ClientOption {
 // NewSSEMCPClient creates a new SSE-based MCP client with the given base URL.
 // Returns an error if the URL is invalid.
 func NewSSEMCPClient(baseURL string, options ...transport.ClientOption) (*Client, error) {
-
 	sseTransport, err := transport.NewSSE(baseURL, options...)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create SSE transport: %w", err)
 	}
-
 	return NewClient(sseTransport), nil
 }
 
diff --git a/client/stdio.go b/client/stdio.go
index 199ec14..6e6e1af 100644
--- a/client/stdio.go
+++ b/client/stdio.go
@@ -12,7 +12,7 @@ import (
 // It launches the specified command with given arguments and sets up stdin/stdout pipes for communication.
 // Returns an error if the subprocess cannot be started or the pipes cannot be created.
 //
-// NOTICE: NewStdioMCPClient will start the connection automatically. Don't call the Start method manually.
+// NOTICE: NewStdioMCPClient will start the connection automatically.
 // This is for backward compatibility.
 func NewStdioMCPClient(
 	command string,
@@ -28,7 +28,6 @@ func NewStdioMCPClient(
 // such as setting a custom command function.
 //
 // NOTICE: NewStdioMCPClientWithOptions automatically starts the underlying transport.
-// Don't call the Start method manually.
 // This is for backward compatibility.
 func NewStdioMCPClientWithOptions(
 	command string,
diff --git a/client/transport/constants.go b/client/transport/constants.go
new file mode 100644
index 0000000..2fb5030
--- /dev/null
+++ b/client/transport/constants.go
@@ -0,0 +1,7 @@
+package transport
+
+// Common HTTP header constants used across transports
+const (
+	HeaderKeySessionID       = "Mcp-Session-Id"
+	HeaderKeyProtocolVersion = "Mcp-Protocol-Version"
+)
diff --git a/client/transport/interface.go b/client/transport/interface.go
index 5f8ed61..e6feeb7 100644
--- a/client/transport/interface.go
+++ b/client/transport/interface.go
@@ -47,6 +47,13 @@ type BidirectionalInterface interface {
 	SetRequestHandler(handler RequestHandler)
 }
 
+// HTTPConnection is a Transport that runs over HTTP and supports
+// protocol version headers.
+type HTTPConnection interface {
+	Interface
+	SetProtocolVersion(version string)
+}
+
 type JSONRPCRequest struct {
 	JSONRPC string        `json:"jsonrpc"`
 	ID      mcp.RequestId `json:"id"`
diff --git a/client/transport/oauth.go b/client/transport/oauth.go
index aebbd31..b7c81ba 100644
--- a/client/transport/oauth.go
+++ b/client/transport/oauth.go
@@ -115,7 +115,9 @@ type OAuthHandler struct {
 	metadataFetchErr error
 	metadataOnce     sync.Once
 	baseURL          string
-	expectedState    string // Expected state value for CSRF protection
+
+	mu            sync.RWMutex // Protects expectedState
+	expectedState string       // Expected state value for CSRF protection
 }
 
 // NewOAuthHandler creates a new OAuth handler
@@ -263,9 +265,27 @@ func (h *OAuthHandler) SetBaseURL(baseURL string) {
 
 // GetExpectedState returns the expected state value (for testing purposes)
 func (h *OAuthHandler) GetExpectedState() string {
+	h.mu.RLock()
+	defer h.mu.RUnlock()
 	return h.expectedState
 }
 
+// SetExpectedState sets the expected state value.
+//
+// This can be useful if you cannot maintain an OAuthHandler
+// instance throughout the authentication flow; for example, if
+// the initialization and callback steps are handled in different
+// requests.
+//
+// In such cases, this should be called with the state value generated
+// during the initial authentication request (e.g. by GenerateState)
+// and included in the authorization URL.
+func (h *OAuthHandler) SetExpectedState(expectedState string) {
+	h.mu.Lock()
+	defer h.mu.Unlock()
+	h.expectedState = expectedState
+}
+
 // OAuthError represents a standard OAuth 2.0 error response
 type OAuthError struct {
 	ErrorCode        string `json:"error"`
@@ -547,18 +567,21 @@ var ErrInvalidState = errors.New("invalid state parameter, possible CSRF attack"
 // ProcessAuthorizationResponse processes the authorization response and exchanges the code for a token
 func (h *OAuthHandler) ProcessAuthorizationResponse(ctx context.Context, code, state, codeVerifier string) error {
 	// Validate the state parameter to prevent CSRF attacks
-	if h.expectedState == "" {
+	h.mu.Lock()
+	expectedState := h.expectedState
+	if expectedState == "" {
+		h.mu.Unlock()
 		return errors.New("no expected state found, authorization flow may not have been initiated properly")
 	}
 
-	if state != h.expectedState {
+	if state != expectedState {
+		h.mu.Unlock()
 		return ErrInvalidState
 	}
 
 	// Clear the expected state after validation
-	defer func() {
-		h.expectedState = ""
-	}()
+	h.expectedState = ""
+	h.mu.Unlock()
 
 	metadata, err := h.getServerMetadata(ctx)
 	if err != nil {
@@ -629,7 +652,7 @@ func (h *OAuthHandler) GetAuthorizationURL(ctx context.Context, state, codeChall
 	}
 
 	// Store the state for later validation
-	h.expectedState = state
+	h.SetExpectedState(state)
 
 	params := url.Values{}
 	params.Set("response_type", "code")
diff --git a/client/transport/sse.go b/client/transport/sse.go
index ffe3247..305c931 100644
--- a/client/transport/sse.go
+++ b/client/transport/sse.go
@@ -16,6 +16,7 @@ import (
 	"time"
 
 	"github.com/mark3labs/mcp-go/mcp"
+	"github.com/mark3labs/mcp-go/util"
 )
 
 // SSE implements the transport layer of the MCP protocol using Server-Sent Events (SSE).
@@ -33,10 +34,14 @@ type SSE struct {
 	endpointChan   chan struct{}
 	headers        map[string]string
 	headerFunc     HTTPHeaderFunc
+	logger         util.Logger
 
-	started         atomic.Bool
-	closed          atomic.Bool
-	cancelSSEStream context.CancelFunc
+	started          atomic.Bool
+	closed           atomic.Bool
+	cancelSSEStream  context.CancelFunc
+	protocolVersion  atomic.Value // string
+	onConnectionLost func(error)
+	connectionLostMu sync.RWMutex
 
 	// OAuth support
 	oauthHandler *OAuthHandler
@@ -44,6 +49,13 @@ type SSE struct {
 
 type ClientOption func(*SSE)
 
+// WithSSELogger sets a custom logger for the SSE client.
+func WithSSELogger(logger util.Logger) ClientOption {
+	return func(sc *SSE) {
+		sc.logger = logger
+	}
+}
+
 func WithHeaders(headers map[string]string) ClientOption {
 	return func(sc *SSE) {
 		sc.headers = headers
@@ -82,6 +94,7 @@ func NewSSE(baseURL string, options ...ClientOption) (*SSE, error) {
 		responses:    make(map[string]chan *JSONRPCResponse),
 		endpointChan: make(chan struct{}),
 		headers:      make(map[string]string),
+		logger:       util.DefaultLogger(),
 	}
 
 	for _, opt := range options {
@@ -101,7 +114,6 @@ func NewSSE(baseURL string, options ...ClientOption) (*SSE, error) {
 // Start initiates the SSE connection to the server and waits for the endpoint information.
 // Returns an error if the connection fails or times out waiting for the endpoint.
 func (c *SSE) Start(ctx context.Context) error {
-
 	if c.started.Load() {
 		return fmt.Errorf("has already started")
 	}
@@ -110,7 +122,6 @@ func (c *SSE) Start(ctx context.Context) error {
 	c.cancelSSEStream = cancel
 
 	req, err := http.NewRequestWithContext(ctx, "GET", c.baseURL.String(), nil)
-
 	if err != nil {
 		return fmt.Errorf("failed to create request: %w", err)
 	}
@@ -203,8 +214,21 @@ func (c *SSE) readSSE(reader io.ReadCloser) {
 				}
 				break
 			}
+			// Checking whether the connection was terminated due to NO_ERROR in HTTP2 based on RFC9113
+			// Only handle NO_ERROR specially if onConnectionLost handler is set to maintain backward compatibility
+			if strings.Contains(err.Error(), "NO_ERROR") {
+				c.connectionLostMu.RLock()
+				handler := c.onConnectionLost
+				c.connectionLostMu.RUnlock()
+
+				if handler != nil {
+					// This is not actually an error - HTTP2 idle timeout disconnection
+					handler(err)
+					return
+				}
+			}
 			if !c.closed.Load() {
-				fmt.Printf("SSE stream error: %v\n", err)
+				c.logger.Errorf("SSE stream error: %v", err)
 			}
 			return
 		}
@@ -240,11 +264,11 @@ func (c *SSE) handleSSEEvent(event, data string) {
 	case "endpoint":
 		endpoint, err := c.baseURL.Parse(data)
 		if err != nil {
-			fmt.Printf("Error parsing endpoint URL: %v\n", err)
+			c.logger.Errorf("Error parsing endpoint URL: %v", err)
 			return
 		}
 		if endpoint.Host != c.baseURL.Host {
-			fmt.Printf("Endpoint origin does not match connection origin\n")
+			c.logger.Errorf("Endpoint origin does not match connection origin")
 			return
 		}
 		c.endpoint = endpoint
@@ -253,7 +277,7 @@ func (c *SSE) handleSSEEvent(event, data string) {
 	case "message":
 		var baseMessage JSONRPCResponse
 		if err := json.Unmarshal([]byte(data), &baseMessage); err != nil {
-			fmt.Printf("Error unmarshaling message: %v\n", err)
+			c.logger.Errorf("Error unmarshaling message: %v", err)
 			return
 		}
 
@@ -293,13 +317,18 @@ func (c *SSE) SetNotificationHandler(handler func(notification mcp.JSONRPCNotifi
 	c.onNotification = handler
 }
 
+func (c *SSE) SetConnectionLostHandler(handler func(error)) {
+	c.connectionLostMu.Lock()
+	defer c.connectionLostMu.Unlock()
+	c.onConnectionLost = handler
+}
+
 // SendRequest sends a JSON-RPC request to the server and waits for a response.
 // Returns the raw JSON response message or an error if the request fails.
 func (c *SSE) SendRequest(
 	ctx context.Context,
 	request JSONRPCRequest,
 ) (*JSONRPCResponse, error) {
-
 	if !c.started.Load() {
 		return nil, fmt.Errorf("transport not started yet")
 	}
@@ -324,6 +353,12 @@ func (c *SSE) SendRequest(
 
 	// Set headers
 	req.Header.Set("Content-Type", "application/json")
+	// Set protocol version header if negotiated
+	if v := c.protocolVersion.Load(); v != nil {
+		if version, ok := v.(string); ok && version != "" {
+			req.Header.Set(HeaderKeyProtocolVersion, version)
+		}
+	}
 	for k, v := range c.headers {
 		req.Header.Set(k, v)
 	}
@@ -434,6 +469,11 @@ func (c *SSE) GetSessionId() string {
 	return ""
 }
 
+// SetProtocolVersion sets the negotiated protocol version for this connection.
+func (c *SSE) SetProtocolVersion(version string) {
+	c.protocolVersion.Store(version)
+}
+
 // SendNotification sends a JSON-RPC notification to the server without expecting a response.
 func (c *SSE) SendNotification(ctx context.Context, notification mcp.JSONRPCNotification) error {
 	if c.endpoint == nil {
@@ -456,6 +496,12 @@ func (c *SSE) SendNotification(ctx context.Context, notification mcp.JSONRPCNoti
 	}
 
 	req.Header.Set("Content-Type", "application/json")
+	// Set protocol version header if negotiated
+	if v := c.protocolVersion.Load(); v != nil {
+		if version, ok := v.(string); ok && version != "" {
+			req.Header.Set(HeaderKeyProtocolVersion, version)
+		}
+	}
 	// Set custom HTTP headers
 	for k, v := range c.headers {
 		req.Header.Set(k, v)
diff --git a/client/transport/stdio.go b/client/transport/stdio.go
index 7602e24..ddb11c0 100644
--- a/client/transport/stdio.go
+++ b/client/transport/stdio.go
@@ -4,6 +4,7 @@ import (
 	"bufio"
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"os"
@@ -11,6 +12,7 @@ import (
 	"sync"
 
 	"github.com/mark3labs/mcp-go/mcp"
+	"github.com/mark3labs/mcp-go/util"
 )
 
 // Stdio implements the transport layer of the MCP protocol using stdio communication.
@@ -25,7 +27,7 @@ type Stdio struct {
 	cmd            *exec.Cmd
 	cmdFunc        CommandFunc
 	stdin          io.WriteCloser
-	stdout         *bufio.Reader
+	stdout         *bufio.Scanner
 	stderr         io.ReadCloser
 	responses      map[string]chan *JSONRPCResponse
 	mu             sync.RWMutex
@@ -36,6 +38,7 @@ type Stdio struct {
 	requestMu      sync.RWMutex
 	ctx            context.Context
 	ctxMu          sync.RWMutex
+	logger         util.Logger
 }
 
 // StdioOption defines a function that configures a Stdio transport instance.
@@ -56,18 +59,26 @@ func WithCommandFunc(f CommandFunc) StdioOption {
 	}
 }
 
+// WithCommandLogger sets a custom logger for the stdio transport.
+func WithCommandLogger(logger util.Logger) StdioOption {
+	return func(s *Stdio) {
+		s.logger = logger
+	}
+}
+
 // NewIO returns a new stdio-based transport using existing input, output, and
 // logging streams instead of spawning a subprocess.
 // This is useful for testing and simulating client behavior.
 func NewIO(input io.Reader, output io.WriteCloser, logging io.ReadCloser) *Stdio {
 	return &Stdio{
 		stdin:  output,
-		stdout: bufio.NewReader(input),
+		stdout: bufio.NewScanner(input),
 		stderr: logging,
 
 		responses: make(map[string]chan *JSONRPCResponse),
 		done:      make(chan struct{}),
 		ctx:       context.Background(),
+		logger:    util.DefaultLogger(),
 	}
 }
 
@@ -101,6 +112,7 @@ func NewStdioWithOptions(
 		responses: make(map[string]chan *JSONRPCResponse),
 		done:      make(chan struct{}),
 		ctx:       context.Background(),
+		logger:    util.DefaultLogger(),
 	}
 
 	for _, opt := range opts {
@@ -168,7 +180,7 @@ func (c *Stdio) spawnCommand(ctx context.Context) error {
 	c.cmd = cmd
 	c.stdin = stdin
 	c.stderr = stderr
-	c.stdout = bufio.NewReader(stdout)
+	c.stdout = bufio.NewScanner(stdout)
 
 	if err := cmd.Start(); err != nil {
 		return fmt.Errorf("failed to start command: %w", err)
@@ -188,11 +200,15 @@ func (c *Stdio) Close() error {
 	// cancel all in-flight request
 	close(c.done)
 
-	if err := c.stdin.Close(); err != nil {
-		return fmt.Errorf("failed to close stdin: %w", err)
+	if c.stdin != nil {
+		if err := c.stdin.Close(); err != nil {
+			return fmt.Errorf("failed to close stdin: %w", err)
+		}
 	}
-	if err := c.stderr.Close(); err != nil {
-		return fmt.Errorf("failed to close stderr: %w", err)
+	if c.stderr != nil {
+		if err := c.stderr.Close(); err != nil {
+			return fmt.Errorf("failed to close stderr: %w", err)
+		}
 	}
 
 	if c.cmd != nil {
@@ -235,14 +251,15 @@ func (c *Stdio) readResponses() {
 		case <-c.done:
 			return
 		default:
-			line, err := c.stdout.ReadString('\n')
-			if err != nil {
-				if err != io.EOF {
-					fmt.Printf("Error reading response: %v\n", err)
+			if !c.stdout.Scan() {
+				err := c.stdout.Err()
+				if err != nil && !errors.Is(err, context.Canceled) {
+					c.logger.Errorf("Error reading from stdout: %v", err)
 				}
 				return
 			}
 
+			line := c.stdout.Text()
 			// First try to parse as a generic message to check for ID field
 			var baseMessage struct {
 				JSONRPC string         `json:"jsonrpc"`
@@ -428,7 +445,6 @@ func (c *Stdio) handleIncomingRequest(request JSONRPCRequest) {
 		}
 
 		response, err := handler(ctx, request)
-
 		if err != nil {
 			errorResponse := JSONRPCResponse{
 				JSONRPC: mcp.JSONRPC_VERSION,
@@ -456,13 +472,13 @@ func (c *Stdio) handleIncomingRequest(request JSONRPCRequest) {
 func (c *Stdio) sendResponse(response JSONRPCResponse) {
 	responseBytes, err := json.Marshal(response)
 	if err != nil {
-		fmt.Printf("Error marshaling response: %v\n", err)
+		c.logger.Errorf("Error marshaling response: %v", err)
 		return
 	}
 	responseBytes = append(responseBytes, '\n')
 
 	if _, err := c.stdin.Write(responseBytes); err != nil {
-		fmt.Printf("Error writing response: %v\n", err)
+		c.logger.Errorf("Error writing response: %v", err)
 	}
 }
 
diff --git a/client/transport/streamable_http.go b/client/transport/streamable_http.go
index 245baba..9894224 100644
--- a/client/transport/streamable_http.go
+++ b/client/transport/streamable_http.go
@@ -68,12 +68,18 @@ func WithHTTPOAuth(config OAuthConfig) StreamableHTTPCOption {
 	}
 }
 
-func WithLogger(logger util.Logger) StreamableHTTPCOption {
+// WithHTTPLogger sets a custom logger for the StreamableHTTP transport.
+func WithHTTPLogger(logger util.Logger) StreamableHTTPCOption {
 	return func(sc *StreamableHTTP) {
 		sc.logger = logger
 	}
 }
 
+// Deprecated: Use [WithHTTPLogger] instead.
+func WithLogger(logger util.Logger) StreamableHTTPCOption {
+	return WithHTTPLogger(logger)
+}
+
 // WithSession creates a client with a pre-configured session
 func WithSession(sessionID string) StreamableHTTPCOption {
 	return func(sc *StreamableHTTP) {
@@ -90,10 +96,8 @@ func WithSession(sessionID string) StreamableHTTPCOption {
 // https://modelcontextprotocol.io/specification/2025-03-26/basic/transports
 //
 // The current implementation does not support the following features:
-//   - batching
 //   - resuming stream
 //     (https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#resumability-and-redelivery)
-//   - server -> client request
 type StreamableHTTP struct {
 	serverURL           *url.URL
 	httpClient          *http.Client
@@ -102,7 +106,8 @@ type StreamableHTTP struct {
 	logger              util.Logger
 	getListeningEnabled bool
 
-	sessionID atomic.Value // string
+	sessionID       atomic.Value // string
+	protocolVersion atomic.Value // string
 
 	initialized     chan struct{}
 	initializedOnce sync.Once
@@ -110,6 +115,10 @@ type StreamableHTTP struct {
 	notificationHandler func(mcp.JSONRPCNotification)
 	notifyMu            sync.RWMutex
 
+	// Request handler for incoming server-to-client requests (like sampling)
+	requestHandler RequestHandler
+	requestMu      sync.RWMutex
+
 	closed chan struct{}
 
 	// OAuth support
@@ -194,7 +203,13 @@ func (c *StreamableHTTP) Close() error {
 				c.logger.Errorf("failed to create close request: %v", err)
 				return
 			}
-			req.Header.Set(headerKeySessionID, sessionId)
+			req.Header.Set(HeaderKeySessionID, sessionId)
+			// Set protocol version header if negotiated
+			if v := c.protocolVersion.Load(); v != nil {
+				if version, ok := v.(string); ok && version != "" {
+					req.Header.Set(HeaderKeyProtocolVersion, version)
+				}
+			}
 			res, err := c.httpClient.Do(req)
 			if err != nil {
 				c.logger.Errorf("failed to send close request: %v", err)
@@ -207,9 +222,10 @@ func (c *StreamableHTTP) Close() error {
 	return nil
 }
 
-const (
-	headerKeySessionID = "Mcp-Session-Id"
-)
+// SetProtocolVersion sets the negotiated protocol version for this connection.
+func (c *StreamableHTTP) SetProtocolVersion(version string) {
+	c.protocolVersion.Store(version)
+}
 
 // ErrOAuthAuthorizationRequired is a sentinel error for OAuth authorization required
 var ErrOAuthAuthorizationRequired = errors.New("no valid token available, authorization required")
@@ -283,7 +299,7 @@ func (c *StreamableHTTP) SendRequest(
 	if request.Method == string(mcp.MethodInitialize) {
 		// saved the received session ID in the response
 		// empty session ID is allowed
-		if sessionID := resp.Header.Get(headerKeySessionID); sessionID != "" {
+		if sessionID := resp.Header.Get(HeaderKeySessionID); sessionID != "" {
 			c.sessionID.Store(sessionID)
 		}
 
@@ -335,7 +351,13 @@ func (c *StreamableHTTP) sendHTTP(
 	req.Header.Set("Accept", acceptType)
 	sessionID := c.sessionID.Load().(string)
 	if sessionID != "" {
-		req.Header.Set(headerKeySessionID, sessionID)
+		req.Header.Set(HeaderKeySessionID, sessionID)
+	}
+	// Set protocol version header if negotiated
+	if v := c.protocolVersion.Load(); v != nil {
+		if version, ok := v.(string); ok && version != "" {
+			req.Header.Set(HeaderKeyProtocolVersion, version)
+		}
 	}
 	for k, v := range c.headers {
 		req.Header.Set(k, v)
@@ -384,20 +406,26 @@ func (c *StreamableHTTP) handleSSEResponse(ctx context.Context, reader io.ReadCl
 	// Create a channel for this specific request
 	responseChan := make(chan *JSONRPCResponse, 1)
 
+	// Add timeout context for request processing if not already set
+	if deadline, ok := ctx.Deadline(); !ok || time.Until(deadline) > 30*time.Second {
+		var cancel context.CancelFunc
+		ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
+		defer cancel()
+	}
+
 	ctx, cancel := context.WithCancel(ctx)
 	defer cancel()
 
 	// Start a goroutine to process the SSE stream
 	go func() {
-		// only close responseChan after readingSSE()
+		// Ensure this goroutine respects the context
 		defer close(responseChan)
 
 		c.readSSE(ctx, reader, func(event, data string) {
-			// (unsupported: batching)
-
+			// Try to unmarshal as a response first
 			var message JSONRPCResponse
 			if err := json.Unmarshal([]byte(data), &message); err != nil {
-				c.logger.Errorf("failed to unmarshal message: %v", err)
+				c.logger.Infof("failed to unmarshal message (non-fatal): %v", err, "message", data)
 				return
 			}
 
@@ -416,6 +444,19 @@ func (c *StreamableHTTP) handleSSEResponse(ctx context.Context, reader io.ReadCl
 				return
 			}
 
+			// Check if this is actually a request from the server by looking for method field
+			var rawMessage map[string]json.RawMessage
+			if err := json.Unmarshal([]byte(data), &rawMessage); err == nil {
+				if _, hasMethod := rawMessage["method"]; hasMethod && !message.ID.IsNil() {
+					var request JSONRPCRequest
+					if err := json.Unmarshal([]byte(data), &request); err == nil {
+						// This is a request from the server
+						c.handleIncomingRequest(ctx, request)
+						return
+					}
+				}
+			}
+
 			if !ignoreResponse {
 				responseChan <- &message
 			}
@@ -536,6 +577,13 @@ func (c *StreamableHTTP) SetNotificationHandler(handler func(mcp.JSONRPCNotifica
 	c.notificationHandler = handler
 }
 
+// SetRequestHandler sets the handler for incoming requests from the server.
+func (c *StreamableHTTP) SetRequestHandler(handler RequestHandler) {
+	c.requestMu.Lock()
+	defer c.requestMu.Unlock()
+	c.requestHandler = handler
+}
+
 func (c *StreamableHTTP) GetSessionId() string {
 	return c.sessionID.Load().(string)
 }
@@ -553,7 +601,11 @@ func (c *StreamableHTTP) IsOAuthEnabled() bool {
 func (c *StreamableHTTP) listenForever(ctx context.Context) {
 	c.logger.Infof("listening to server forever")
 	for {
-		err := c.createGETConnectionToServer(ctx)
+		// Add timeout for individual connection attempts
+		connectCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
+		err := c.createGETConnectionToServer(connectCtx)
+		cancel()
+
 		if errors.Is(err, ErrGetMethodNotAllowed) {
 			// server does not support listening
 			c.logger.Errorf("server does not support listening")
@@ -569,7 +621,13 @@ func (c *StreamableHTTP) listenForever(ctx context.Context) {
 		if err != nil {
 			c.logger.Errorf("failed to listen to server. retry in 1 second: %v", err)
 		}
-		time.Sleep(retryInterval)
+
+		// Use context-aware sleep
+		select {
+		case <-time.After(retryInterval):
+		case <-ctx.Done():
+			return
+		}
 	}
 }
 
@@ -616,6 +674,116 @@ func (c *StreamableHTTP) createGETConnectionToServer(ctx context.Context) error
 	return nil
 }
 
+// handleIncomingRequest processes requests from the server (like sampling requests)
+func (c *StreamableHTTP) handleIncomingRequest(ctx context.Context, request JSONRPCRequest) {
+	c.requestMu.RLock()
+	handler := c.requestHandler
+	c.requestMu.RUnlock()
+
+	if handler == nil {
+		c.logger.Errorf("received request from server but no handler set: %s", request.Method)
+		// Send method not found error
+		errorResponse := &JSONRPCResponse{
+			JSONRPC: "2.0",
+			ID:      request.ID,
+			Error: &struct {
+				Code    int             `json:"code"`
+				Message string          `json:"message"`
+				Data    json.RawMessage `json:"data"`
+			}{
+				Code:    -32601, // Method not found
+				Message: fmt.Sprintf("no handler configured for method: %s", request.Method),
+			},
+		}
+		c.sendResponseToServer(ctx, errorResponse)
+		return
+	}
+
+	// Handle the request in a goroutine to avoid blocking the SSE reader
+	go func() {
+		// Create a new context with timeout for request handling, respecting parent context
+		requestCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
+		defer cancel()
+
+		response, err := handler(requestCtx, request)
+		if err != nil {
+			c.logger.Errorf("error handling request %s: %v", request.Method, err)
+
+			// Determine appropriate JSON-RPC error code based on error type
+			var errorCode int
+			var errorMessage string
+
+			// Check for specific sampling-related errors
+			if errors.Is(err, context.Canceled) {
+				errorCode = -32800 // Request cancelled
+				errorMessage = "request was cancelled"
+			} else if errors.Is(err, context.DeadlineExceeded) {
+				errorCode = -32800 // Request timeout
+				errorMessage = "request timed out"
+			} else {
+				// Generic error cases
+				switch request.Method {
+				case string(mcp.MethodSamplingCreateMessage):
+					errorCode = -32603 // Internal error
+					errorMessage = fmt.Sprintf("sampling request failed: %v", err)
+				default:
+					errorCode = -32603 // Internal error
+					errorMessage = err.Error()
+				}
+			}
+
+			// Send error response
+			errorResponse := &JSONRPCResponse{
+				JSONRPC: "2.0",
+				ID:      request.ID,
+				Error: &struct {
+					Code    int             `json:"code"`
+					Message string          `json:"message"`
+					Data    json.RawMessage `json:"data"`
+				}{
+					Code:    errorCode,
+					Message: errorMessage,
+				},
+			}
+			c.sendResponseToServer(requestCtx, errorResponse)
+			return
+		}
+
+		if response != nil {
+			c.sendResponseToServer(requestCtx, response)
+		}
+	}()
+}
+
+// sendResponseToServer sends a response back to the server via HTTP POST
+func (c *StreamableHTTP) sendResponseToServer(ctx context.Context, response *JSONRPCResponse) {
+	if response == nil {
+		c.logger.Errorf("cannot send nil response to server")
+		return
+	}
+
+	responseBody, err := json.Marshal(response)
+	if err != nil {
+		c.logger.Errorf("failed to marshal response: %v", err)
+		return
+	}
+
+	ctx, cancel := c.contextAwareOfClientClose(ctx)
+	defer cancel()
+
+	resp, err := c.sendHTTP(ctx, http.MethodPost, bytes.NewReader(responseBody), "application/json")
+	if err != nil {
+		c.logger.Errorf("failed to send response to server: %v", err)
+		return
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
+		body, _ := io.ReadAll(resp.Body)
+		c.logger.Errorf("server rejected response with status %d: %s", resp.StatusCode, body)
+	}
+}
+
 func (c *StreamableHTTP) contextAwareOfClientClose(ctx context.Context) (context.Context, context.CancelFunc) {
 	newCtx, cancel := context.WithCancel(ctx)
 	go func() {
diff --git a/examples/oauth_client/main.go b/examples/oauth_client/main.go
index 639d8cb..27d3b61 100644
--- a/examples/oauth_client/main.go
+++ b/examples/oauth_client/main.go
@@ -129,9 +129,11 @@ func maybeAuthorize(err error) {
 			log.Fatalf("Failed to generate state: %v", err)
 		}
 
-		err = oauthHandler.RegisterClient(context.Background(), "mcp-go-oauth-example")
-		if err != nil {
-			log.Fatalf("Failed to register client: %v", err)
+		if oauthHandler.GetClientID() == "" {
+			err = oauthHandler.RegisterClient(context.Background(), "mcp-go-oauth-example")
+			if err != nil {
+				log.Fatalf("Failed to register client: %v", err)
+			}
 		}
 
 		// Get the authorization URL
diff --git a/examples/sampling_client/main.go b/examples/sampling_client/main.go
index 67b3840..a655fde 100644
--- a/examples/sampling_client/main.go
+++ b/examples/sampling_client/main.go
@@ -5,6 +5,8 @@ import (
 	"fmt"
 	"log"
 	"os"
+	"os/signal"
+	"syscall"
 
 	"github.com/mark3labs/mcp-go/client"
 	"github.com/mark3labs/mcp-go/client/transport"
@@ -28,7 +30,7 @@ func (h *MockSamplingHandler) CreateMessage(ctx context.Context, request mcp.Cre
 	switch content := userMessage.Content.(type) {
 	case mcp.TextContent:
 		userText = content.Text
-	case map[string]interface{}:
+	case map[string]any:
 		// Handle case where content is unmarshaled as a map
 		if text, ok := content["text"].(string); ok {
 			userText = text
@@ -89,7 +91,25 @@ func main() {
 	if err := mcpClient.Start(ctx); err != nil {
 		log.Fatalf("Failed to start client: %v", err)
 	}
-	defer mcpClient.Close()
+
+	// Setup graceful shutdown
+	sigChan := make(chan os.Signal, 1)
+	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+
+	// Create a context that cancels on signal
+	ctx, cancel := context.WithCancel(ctx)
+	go func() {
+		<-sigChan
+		log.Println("Received shutdown signal, closing client...")
+		cancel()
+	}()
+
+	// Move defer after error checking
+	defer func() {
+		if err := mcpClient.Close(); err != nil {
+			log.Printf("Error closing client: %v", err)
+		}
+	}()
 
 	// Initialize the connection
 	initResult, err := mcpClient.Initialize(ctx, mcp.InitializeRequest{
diff --git a/examples/sampling_http_client/README.md b/examples/sampling_http_client/README.md
new file mode 100644
index 0000000..e4cf0ea
--- /dev/null
+++ b/examples/sampling_http_client/README.md
@@ -0,0 +1,95 @@
+# HTTP Sampling Client Example
+
+This example demonstrates how to create an MCP client using HTTP transport that supports sampling requests from the server.
+
+## Overview
+
+This client:
+- Connects to an MCP server via HTTP/HTTPS transport
+- Declares sampling capability during initialization
+- Handles incoming sampling requests from the server
+- Uses a mock LLM to generate responses (replace with real LLM integration)
+
+## Usage
+
+1. Start an MCP server that supports sampling (e.g., using the `sampling_server` example)
+
+2. Update the server URL in `main.go`:
+   ```go
+   httpClient, err := client.NewStreamableHttpClient(
+       "http://your-server:port", // Replace with your server URL
+   )
+   ```
+
+3. Run the client:
+   ```bash
+   go run main.go
+   ```
+
+## Key Features
+
+### HTTP Transport with Sampling
+The client creates the HTTP transport directly and then wraps it with a client that supports sampling:
+
+```go
+httpTransport, err := transport.NewStreamableHTTP("http://localhost:8080")
+mcpClient := client.NewClient(httpTransport, client.WithSamplingHandler(samplingHandler))
+```
+
+### Sampling Handler
+The `MockSamplingHandler` implements the `client.SamplingHandler` interface:
+
+```go
+type MockSamplingHandler struct{}
+
+func (h *MockSamplingHandler) CreateMessage(ctx context.Context, request mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) {
+    // Process the sampling request and return LLM response
+    // In production, integrate with OpenAI, Anthropic, or other LLM APIs
+}
+```
+
+### Client Configuration
+The client is configured with sampling capabilities:
+
+```go
+mcpClient := client.NewClient(
+    httpTransport,
+    client.WithSamplingHandler(samplingHandler),
+)
+// Sampling capability is automatically declared when a handler is provided
+```
+
+## Real Implementation
+
+For a production implementation, replace the `MockSamplingHandler` with a real LLM client:
+
+```go
+type RealSamplingHandler struct {
+    client *openai.Client // or other LLM client
+}
+
+func (h *RealSamplingHandler) CreateMessage(ctx context.Context, request mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) {
+    // Convert MCP request to LLM API format
+    // Call LLM API
+    // Convert response back to MCP format
+    // Return the result
+}
+```
+
+## HTTP-Specific Features
+
+The HTTP transport supports:
+- Standard HTTP headers for authentication and customization
+- OAuth 2.0 authentication (using `WithHTTPOAuth`)
+- Custom headers (using `WithHTTPHeaders`)
+- Server-side events (SSE) for bidirectional communication
+- Proper error handling with HTTP status codes
+- Session management via HTTP headers
+
+## Testing
+
+The implementation includes comprehensive tests in `client/transport/streamable_http_sampling_test.go` that verify:
+- Sampling request handling
+- Error scenarios
+- Bidirectional interface compliance
+- HTTP-specific error codes and responses
\ No newline at end of file
diff --git a/examples/sampling_http_client/main.go b/examples/sampling_http_client/main.go
new file mode 100644
index 0000000..946223f
--- /dev/null
+++ b/examples/sampling_http_client/main.go
@@ -0,0 +1,116 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"log"
+	"os"
+	"os/signal"
+	"syscall"
+
+	"github.com/mark3labs/mcp-go/client"
+	"github.com/mark3labs/mcp-go/client/transport"
+	"github.com/mark3labs/mcp-go/mcp"
+)
+
+// MockSamplingHandler implements client.SamplingHandler for demonstration.
+// In a real implementation, this would integrate with an actual LLM API.
+type MockSamplingHandler struct{}
+
+func (h *MockSamplingHandler) CreateMessage(ctx context.Context, request mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) {
+	// Extract the user's message
+	if len(request.Messages) == 0 {
+		return nil, fmt.Errorf("no messages provided")
+	}
+
+	// Get the last user message
+	lastMessage := request.Messages[len(request.Messages)-1]
+	userText := ""
+	if textContent, ok := lastMessage.Content.(mcp.TextContent); ok {
+		userText = textContent.Text
+	}
+
+	// Generate a mock response
+	responseText := fmt.Sprintf("Mock LLM response to: '%s'", userText)
+
+	log.Printf("Mock LLM generating response: %s", responseText)
+
+	result := &mcp.CreateMessageResult{
+		SamplingMessage: mcp.SamplingMessage{
+			Role: mcp.RoleAssistant,
+			Content: mcp.TextContent{
+				Type: "text",
+				Text: responseText,
+			},
+		},
+		Model:      "mock-model-v1",
+		StopReason: "endTurn",
+	}
+
+	return result, nil
+}
+
+func main() {
+	// Create sampling handler
+	samplingHandler := &MockSamplingHandler{}
+
+	// Create HTTP transport directly
+	httpTransport, err := transport.NewStreamableHTTP(
+		"http://localhost:8080", // Replace with your MCP server URL
+		// You can add HTTP-specific options here like headers, OAuth, etc.
+	)
+	if err != nil {
+		log.Fatalf("Failed to create HTTP transport: %v", err)
+	}
+	defer httpTransport.Close()
+
+	// Create client with sampling support
+	mcpClient := client.NewClient(
+		httpTransport,
+		client.WithSamplingHandler(samplingHandler),
+	)
+
+	// Start the client
+	ctx := context.Background()
+	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{
+				// Sampling capability will be automatically added by the client
+			},
+			ClientInfo: mcp.Implementation{
+				Name:    "sampling-http-client",
+				Version: "1.0.0",
+			},
+		},
+	}
+
+	_, err = mcpClient.Initialize(ctx, initRequest)
+	if err != nil {
+		log.Fatalf("Failed to initialize MCP session: %v", err)
+	}
+
+	log.Println("HTTP MCP client with sampling support started successfully!")
+	log.Println("The client is now ready to handle sampling requests from the server.")
+	log.Println("When the server sends a sampling request, the MockSamplingHandler will process it.")
+
+	// In a real application, you would keep the client running to handle sampling requests
+	// For this example, we'll just demonstrate that it's working
+
+	// Keep the client running (in a real app, you'd have your main application logic here)
+	sigChan := make(chan os.Signal, 1)
+	signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
+
+	select {
+	case <-ctx.Done():
+		log.Println("Client context cancelled")
+	case <-sigChan:
+		log.Println("Received shutdown signal")
+	}
+}
diff --git a/examples/sampling_http_server/README.md b/examples/sampling_http_server/README.md
new file mode 100644
index 0000000..64be58c
--- /dev/null
+++ b/examples/sampling_http_server/README.md
@@ -0,0 +1,138 @@
+# HTTP Sampling Server Example
+
+This example demonstrates how to create an MCP server using HTTP transport that can send sampling requests to clients.
+
+## Overview
+
+This server:
+- Runs on HTTP transport (port 8080 by default)
+- Declares sampling capability during initialization  
+- Can send sampling requests to connected clients via Server-Sent Events (SSE)
+- Receives sampling responses from clients via HTTP POST
+- Includes tools that demonstrate sampling functionality
+
+## Usage
+
+1. Start the server:
+   ```bash
+   go run main.go
+   ```
+
+2. The server will be available at: `http://localhost:8080/mcp`
+
+3. Connect with an HTTP client that supports sampling (like the `sampling_http_client` example)
+
+## Tools Available
+
+### `ask_llm`
+Demonstrates server-initiated sampling:
+- Takes a question and optional system prompt
+- Sends sampling request to client
+- Returns the LLM's response
+
+### `echo` 
+Simple tool for testing basic functionality:
+- Echoes back the input message
+- Doesn't require sampling
+
+## How Sampling Works
+
+### Server → Client Flow
+1. **Tool Invocation**: Client calls `ask_llm` tool
+2. **Sampling Request**: Server creates sampling request with user's question
+3. **SSE Transmission**: Server sends JSON-RPC request to client via SSE stream
+4. **Client Processing**: Client's sampling handler processes the request
+5. **HTTP Response**: Client sends JSON-RPC response back via HTTP POST
+6. **Tool Response**: Server returns the LLM response to the original tool caller
+
+### Communication Architecture
+```
+Client (HTTP + SSE) ←→ Server (HTTP)
+     │                       │
+     ├─ POST: Tool Call ──→  │
+     │                       │
+     │  ←── SSE: Sampling ───┤ 
+     │      Request          │
+     │                       │
+     ├─ POST: Sampling ───→  │
+     │      Response         │
+     │                       │
+     │  ←── HTTP: Tool ──────┤
+           Response
+```
+
+## Key Features
+
+### Bidirectional Communication
+- **SSE Stream**: Server → Client requests (sampling, notifications)
+- **HTTP POST**: Client → Server responses and requests
+
+### Session Management
+- Session ID tracking for request/response correlation
+- Proper session lifecycle management
+- Session validation for security
+
+### Error Handling
+- JSON-RPC error codes for different failure scenarios
+- Timeout handling for sampling requests
+- Queue overflow protection
+
+### HTTP-Specific Features
+- Standard MCP headers (`Mcp-Session-Id`, `Mcp-Protocol-Version`)
+- Content-Type validation
+- Proper HTTP status codes
+- SSE event formatting
+
+## Testing
+
+You can test the server using the `sampling_http_client` example:
+
+1. Start this server:
+   ```bash
+   go run examples/sampling_http_server/main.go
+   ```
+
+2. In another terminal, start the client:
+   ```bash
+   go run examples/sampling_http_client/main.go
+   ```
+
+3. The client will connect and be ready to handle sampling requests from the server.
+
+## Production Considerations
+
+### Security
+- Implement proper authentication/authorization
+- Use HTTPS in production
+- Validate all incoming data
+- Implement rate limiting
+
+### Scalability
+- Consider connection pooling for multiple clients
+- Implement proper session cleanup
+- Monitor memory usage for long-running sessions
+- Add metrics and monitoring
+
+### Reliability
+- Implement request retries
+- Add circuit breakers for failing clients
+- Implement graceful degradation when sampling is unavailable
+- Add comprehensive logging
+
+## Integration
+
+This server can be integrated into existing HTTP infrastructure:
+
+```go
+// Custom HTTP server integration
+mux := http.NewServeMux()
+mux.Handle("/mcp", httpServer)
+mux.Handle("/health", healthHandler)
+
+server := &http.Server{
+    Addr:    ":8080",
+    Handler: mux,
+}
+```
+
+The sampling functionality works seamlessly with other MCP features like tools, resources, and prompts.
\ No newline at end of file
diff --git a/examples/sampling_http_server/main.go b/examples/sampling_http_server/main.go
new file mode 100644
index 0000000..a178acc
--- /dev/null
+++ b/examples/sampling_http_server/main.go
@@ -0,0 +1,150 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"log"
+	"time"
+
+	"github.com/mark3labs/mcp-go/mcp"
+	"github.com/mark3labs/mcp-go/server"
+)
+
+func main() {
+	// Create MCP server with sampling capability
+	mcpServer := server.NewMCPServer("sampling-http-server", "1.0.0")
+
+	// Enable sampling capability
+	mcpServer.EnableSampling()
+
+	// Add a tool that uses sampling to get LLM responses
+	mcpServer.AddTool(mcp.Tool{
+		Name:        "ask_llm",
+		Description: "Ask the LLM a question using sampling over HTTP",
+		InputSchema: mcp.ToolInputSchema{
+			Type: "object",
+			Properties: map[string]any{
+				"question": map[string]any{
+					"type":        "string",
+					"description": "The question to ask the LLM",
+				},
+				"system_prompt": map[string]any{
+					"type":        "string",
+					"description": "Optional system prompt to provide context",
+				},
+			},
+			Required: []string{"question"},
+		},
+	}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+		// Extract parameters
+		question, err := request.RequireString("question")
+		if err != nil {
+			return nil, err
+		}
+
+		systemPrompt := request.GetString("system_prompt", "You are a helpful assistant.")
+
+		// Create sampling request
+		samplingRequest := mcp.CreateMessageRequest{
+			CreateMessageParams: mcp.CreateMessageParams{
+				Messages: []mcp.SamplingMessage{
+					{
+						Role: mcp.RoleUser,
+						Content: mcp.TextContent{
+							Type: "text",
+							Text: question,
+						},
+					},
+				},
+				SystemPrompt: systemPrompt,
+				MaxTokens:    1000,
+				Temperature:  0.7,
+			},
+		}
+
+		// Request sampling from the client with timeout
+		samplingCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)
+		defer cancel()
+
+		serverFromCtx := server.ServerFromContext(ctx)
+		result, err := serverFromCtx.RequestSampling(samplingCtx, samplingRequest)
+		if err != nil {
+			return &mcp.CallToolResult{
+				Content: []mcp.Content{
+					mcp.TextContent{
+						Type: "text",
+						Text: fmt.Sprintf("Error requesting sampling: %v", err),
+					},
+				},
+				IsError: true,
+			}, nil
+		}
+
+		// Extract response text safely
+		var responseText string
+		if textContent, ok := result.Content.(mcp.TextContent); ok {
+			responseText = textContent.Text
+		} else {
+			responseText = fmt.Sprintf("%v", result.Content)
+		}
+
+		// Return the LLM response
+		return &mcp.CallToolResult{
+			Content: []mcp.Content{
+				mcp.TextContent{
+					Type: "text",
+					Text: fmt.Sprintf("LLM Response (model: %s): %s", result.Model, responseText),
+				},
+			},
+		}, nil
+	})
+
+	// Add a simple echo tool for testing
+	mcpServer.AddTool(mcp.Tool{
+		Name:        "echo",
+		Description: "Echo back the input message",
+		InputSchema: mcp.ToolInputSchema{
+			Type: "object",
+			Properties: map[string]any{
+				"message": map[string]any{
+					"type":        "string",
+					"description": "The message to echo back",
+				},
+			},
+			Required: []string{"message"},
+		},
+	}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+		message := request.GetString("message", "")
+
+		return &mcp.CallToolResult{
+			Content: []mcp.Content{
+				mcp.TextContent{
+					Type: "text",
+					Text: fmt.Sprintf("Echo: %s", message),
+				},
+			},
+		}, nil
+	})
+
+	// Create HTTP server
+	httpServer := server.NewStreamableHTTPServer(mcpServer)
+
+	log.Println("Starting HTTP MCP server with sampling support on :8080")
+	log.Println("Endpoint: http://localhost:8080/mcp")
+	log.Println("")
+	log.Println("This server supports sampling over HTTP transport.")
+	log.Println("Clients must:")
+	log.Println("1. Initialize with sampling capability")
+	log.Println("2. Establish SSE connection for bidirectional communication")
+	log.Println("3. Handle incoming sampling requests from the server")
+	log.Println("4. Send responses back via HTTP POST")
+	log.Println("")
+	log.Println("Available tools:")
+	log.Println("- ask_llm: Ask the LLM a question (requires sampling)")
+	log.Println("- echo: Simple echo tool (no sampling required)")
+
+	// Start the server
+	if err := httpServer.Start(":8080"); err != nil {
+		log.Fatalf("Server failed to start: %v", err)
+	}
+}
diff --git a/examples/sampling_server/main.go b/examples/sampling_server/main.go
index c3bcf49..ea887c5 100644
--- a/examples/sampling_server/main.go
+++ b/examples/sampling_server/main.go
@@ -127,11 +127,11 @@ func main() {
 }
 
 // Helper function to extract text from content
-func getTextFromContent(content interface{}) string {
+func getTextFromContent(content any) string {
 	switch c := content.(type) {
 	case mcp.TextContent:
 		return c.Text
-	case map[string]interface{}:
+	case map[string]any:
 		// Handle JSON unmarshaled content
 		if text, ok := c["text"].(string); ok {
 			return text
diff --git a/examples/simple_client/main.go b/examples/simple_client/main.go
index c0f4859..d7497b6 100644
--- a/examples/simple_client/main.go
+++ b/examples/simple_client/main.go
@@ -121,18 +121,24 @@ func main() {
 		serverInfo.ServerInfo.Version)
 	fmt.Printf("Server capabilities: %+v\n", serverInfo.Capabilities)
 
+	// Perform health check using ping
+	fmt.Println("Performing health check...")
+	if err := c.Ping(ctx); err != nil {
+		log.Fatalf("Health check failed: %v", err)
+	}
+	fmt.Println("Server is alive and responding")
+
 	// List available tools if the server supports them
 	if serverInfo.Capabilities.Tools != nil {
 		fmt.Println("Fetching available tools...")
 		toolsRequest := mcp.ListToolsRequest{}
 		toolsResult, err := c.ListTools(ctx, toolsRequest)
 		if err != nil {
-			log.Printf("Failed to list tools: %v", err)
-		} else {
-			fmt.Printf("Server has %d tools available\n", len(toolsResult.Tools))
-			for i, tool := range toolsResult.Tools {
-				fmt.Printf("  %d. %s - %s\n", i+1, tool.Name, tool.Description)
-			}
+			log.Fatalf("Failed to list tools: %v", err)
+		}
+		fmt.Printf("Server has %d tools available\n", len(toolsResult.Tools))
+		for i, tool := range toolsResult.Tools {
+			fmt.Printf("  %d. %s - %s\n", i+1, tool.Name, tool.Description)
 		}
 	}
 
@@ -142,12 +148,11 @@ func main() {
 		resourcesRequest := mcp.ListResourcesRequest{}
 		resourcesResult, err := c.ListResources(ctx, resourcesRequest)
 		if err != nil {
-			log.Printf("Failed to list resources: %v", err)
-		} else {
-			fmt.Printf("Server has %d resources available\n", len(resourcesResult.Resources))
-			for i, resource := range resourcesResult.Resources {
-				fmt.Printf("  %d. %s - %s\n", i+1, resource.URI, resource.Name)
-			}
+			log.Fatalf("Failed to list resources: %v", err)
+		}
+		fmt.Printf("Server has %d resources available\n", len(resourcesResult.Resources))
+		for i, resource := range resourcesResult.Resources {
+			fmt.Printf("  %d. %s - %s\n", i+1, resource.URI, resource.Name)
 		}
 	}
 
diff --git a/examples/structured_input_and_output/README.md b/examples/structured_input_and_output/README.md
new file mode 100644
index 0000000..76de8e1
--- /dev/null
+++ b/examples/structured_input_and_output/README.md
@@ -0,0 +1,55 @@
+# Structured Content Example
+
+This example shows how to return `structuredContent` in tool result with corresponding `OutputSchema`.
+
+Defined in the MCP spec here: https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content
+
+## Usage
+
+Define a struct for your input:
+
+```go
+type WeatherRequest struct {
+    Location string `json:"location,required" jsonschema_description:"City or location"`
+    Units    string `json:"units,omitempty" jsonschema_description:"celsius or fahrenheit" jsonschema:"enum=celsius,enum=fahrenheit"`
+}
+```
+
+Define a struct for your output:
+
+```go
+type WeatherResponse struct {
+    Location    string  `json:"location" jsonschema_description:"The location"`
+    Temperature float64 `json:"temperature" jsonschema_description:"Current temperature"`
+    Conditions  string  `json:"conditions" jsonschema_description:"Weather conditions"`
+}
+```
+
+Add it to your tool:
+
+```go
+tool := mcp.NewTool("get_weather",
+    mcp.WithDescription("Get weather information"),
+    mcp.WithInputSchema[WeatherRequest](),
+    mcp.WithOutputSchema[WeatherResponse](),
+)
+```
+
+Return structured data in tool result:
+
+```go
+func weatherHandler(ctx context.Context, request mcp.CallToolRequest, args WeatherRequest) (*mcp.CallToolResult, error) {
+    response := WeatherResponse{
+        Location:    args.Location,
+        Temperature: 25.0,
+        Conditions:  "Cloudy",
+    }
+    
+    fallbackText := fmt.Sprintf("Weather in %s: %.1f°C, %s", 
+        response.Location, response.Temperature, response.Conditions)
+    
+    return mcp.NewToolResultStructured(response, fallbackText), nil
+}
+```
+
+See [main.go](./main.go) for more examples.
\ No newline at end of file
diff --git a/examples/structured_input_and_output/main.go b/examples/structured_input_and_output/main.go
new file mode 100644
index 0000000..f932def
--- /dev/null
+++ b/examples/structured_input_and_output/main.go
@@ -0,0 +1,151 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"github.com/mark3labs/mcp-go/mcp"
+	"github.com/mark3labs/mcp-go/server"
+)
+
+// Note: The jsonschema_description tag is added to the JSON schema as description
+// Ideally use better descriptions, this is just an example
+type WeatherRequest struct {
+	Location string `json:"location" jsonschema_description:"City or location" jsonschema:"required"`
+	Units    string `json:"units,omitempty" jsonschema_description:"celsius or fahrenheit" jsonschema:"enum=celsius,enum=fahrenheit"`
+}
+
+type WeatherResponse struct {
+	Location    string    `json:"location" jsonschema_description:"Location"`
+	Temperature float64   `json:"temperature" jsonschema_description:"Temperature"`
+	Units       string    `json:"units" jsonschema_description:"Units"`
+	Conditions  string    `json:"conditions" jsonschema_description:"Weather conditions"`
+	Timestamp   time.Time `json:"timestamp" jsonschema_description:"When retrieved"`
+}
+
+type UserProfile struct {
+	ID    string   `json:"id" jsonschema_description:"User ID"`
+	Name  string   `json:"name" jsonschema_description:"Full name"`
+	Email string   `json:"email" jsonschema_description:"Email"`
+	Tags  []string `json:"tags" jsonschema_description:"User tags"`
+}
+
+type UserRequest struct {
+	UserID string `json:"userId" jsonschema_description:"User ID" jsonschema:"required"`
+}
+
+type Asset struct {
+	ID       string  `json:"id" jsonschema_description:"Asset identifier"`
+	Name     string  `json:"name" jsonschema_description:"Asset name"`
+	Value    float64 `json:"value" jsonschema_description:"Current value"`
+	Currency string  `json:"currency" jsonschema_description:"Currency code"`
+}
+
+type AssetListRequest struct {
+	Limit int `json:"limit,omitempty" jsonschema_description:"Number of assets to return" jsonschema:"minimum=1,maximum=100,default=10"`
+}
+
+func main() {
+	s := server.NewMCPServer(
+		"Structured Input/Output Example",
+		"1.0.0",
+		server.WithToolCapabilities(false),
+	)
+
+	// Example 1: Auto-generated schema from struct
+	weatherTool := mcp.NewTool("get_weather",
+		mcp.WithDescription("Get weather with structured output"),
+		mcp.WithInputSchema[WeatherRequest](),
+		mcp.WithOutputSchema[WeatherResponse](),
+	)
+	s.AddTool(weatherTool, mcp.NewStructuredToolHandler(getWeatherHandler))
+
+	// Example 2: Nested struct schema
+	userTool := mcp.NewTool("get_user_profile",
+		mcp.WithDescription("Get user profile"),
+		mcp.WithInputSchema[UserRequest](),
+		mcp.WithOutputSchema[UserProfile](),
+	)
+	s.AddTool(userTool, mcp.NewStructuredToolHandler(getUserProfileHandler))
+
+	// Example 3: Array output - direct array of objects
+	assetsTool := mcp.NewTool("get_assets",
+		mcp.WithDescription("Get list of assets as array"),
+		mcp.WithInputSchema[AssetListRequest](),
+		mcp.WithOutputSchema[[]Asset](),
+	)
+	s.AddTool(assetsTool, mcp.NewStructuredToolHandler(getAssetsHandler))
+
+	// Example 4: Manual result creation
+	manualTool := mcp.NewTool("manual_structured",
+		mcp.WithDescription("Manual structured result"),
+		mcp.WithInputSchema[WeatherRequest](),
+		mcp.WithOutputSchema[WeatherResponse](),
+	)
+	s.AddTool(manualTool, mcp.NewTypedToolHandler(manualWeatherHandler))
+
+	if err := server.ServeStdio(s); err != nil {
+		fmt.Printf("Server error: %v\n", err)
+	}
+}
+
+func getWeatherHandler(ctx context.Context, request mcp.CallToolRequest, args WeatherRequest) (WeatherResponse, error) {
+	temp := 22.5
+	if args.Units == "fahrenheit" {
+		temp = temp*9/5 + 32
+	}
+
+	return WeatherResponse{
+		Location:    args.Location,
+		Temperature: temp,
+		Units:       args.Units,
+		Conditions:  "Cloudy with a chance of meatballs",
+		Timestamp:   time.Now(),
+	}, nil
+}
+
+func getUserProfileHandler(ctx context.Context, request mcp.CallToolRequest, args UserRequest) (UserProfile, error) {
+	return UserProfile{
+		ID:    args.UserID,
+		Name:  "John Doe",
+		Email: "john.doe@example.com",
+		Tags:  []string{"developer", "golang"},
+	}, nil
+}
+
+func getAssetsHandler(ctx context.Context, request mcp.CallToolRequest, args AssetListRequest) ([]Asset, error) {
+	limit := args.Limit
+	if limit <= 0 {
+		limit = 10
+	}
+
+	assets := []Asset{
+		{ID: "btc", Name: "Bitcoin", Value: 45000.50, Currency: "USD"},
+		{ID: "eth", Name: "Ethereum", Value: 3200.75, Currency: "USD"},
+		{ID: "ada", Name: "Cardano", Value: 0.85, Currency: "USD"},
+		{ID: "sol", Name: "Solana", Value: 125.30, Currency: "USD"},
+		{ID: "dot", Name: "Pottedot", Value: 18.45, Currency: "USD"},
+	}
+
+	if limit > len(assets) {
+		limit = len(assets)
+	}
+
+	return assets[:limit], nil
+}
+
+func manualWeatherHandler(ctx context.Context, request mcp.CallToolRequest, args WeatherRequest) (*mcp.CallToolResult, error) {
+	response := WeatherResponse{
+		Location:    args.Location,
+		Temperature: 25.0,
+		Units:       "celsius",
+		Conditions:  "Sunny, yesterday my life was filled with rain",
+		Timestamp:   time.Now(),
+	}
+
+	fallbackText := fmt.Sprintf("Weather in %s: %.1f°C, %s",
+		response.Location, response.Temperature, response.Conditions)
+
+	return mcp.NewToolResultStructured(response, fallbackText), nil
+}
diff --git a/go.mod b/go.mod
index 9b9fe2d..5c89745 100644
--- a/go.mod
+++ b/go.mod
@@ -4,13 +4,18 @@ go 1.23
 
 require (
 	github.com/google/uuid v1.6.0
+	github.com/invopop/jsonschema v0.13.0
 	github.com/spf13/cast v1.7.1
 	github.com/stretchr/testify v1.9.0
 	github.com/yosida95/uritemplate/v3 v3.0.2
 )
 
 require (
+	github.com/bahlo/generic-list-go v0.2.0 // indirect
+	github.com/buger/jsonparser v1.1.1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 31ed86d..70e9c33 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,7 @@
+github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
+github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
+github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
+github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
@@ -6,10 +10,15 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
+github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
@@ -18,6 +27,8 @@ github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
 github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
 github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
+github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
 github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
 github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
diff --git a/mcp/consts.go b/mcp/consts.go
new file mode 100644
index 0000000..66eb380
--- /dev/null
+++ b/mcp/consts.go
@@ -0,0 +1,9 @@
+package mcp
+
+const (
+	ContentTypeText     = "text"
+	ContentTypeImage    = "image"
+	ContentTypeAudio    = "audio"
+	ContentTypeLink     = "resource_link"
+	ContentTypeResource = "resource"
+)
diff --git a/mcp/errors.go b/mcp/errors.go
new file mode 100644
index 0000000..01888bf
--- /dev/null
+++ b/mcp/errors.go
@@ -0,0 +1,25 @@
+package mcp
+
+import "fmt"
+
+// UnsupportedProtocolVersionError is returned when the server responds with
+// a protocol version that the client doesn't support.
+type UnsupportedProtocolVersionError struct {
+	Version string
+}
+
+func (e UnsupportedProtocolVersionError) Error() string {
+	return fmt.Sprintf("unsupported protocol version: %q", e.Version)
+}
+
+// Is implements the errors.Is interface for better error handling
+func (e UnsupportedProtocolVersionError) Is(target error) bool {
+	_, ok := target.(UnsupportedProtocolVersionError)
+	return ok
+}
+
+// IsUnsupportedProtocolVersion checks if an error is an UnsupportedProtocolVersionError
+func IsUnsupportedProtocolVersion(err error) bool {
+	_, ok := err.(UnsupportedProtocolVersionError)
+	return ok
+}
diff --git a/mcp/prompts.go b/mcp/prompts.go
index ea269db..9b0b48e 100644
--- a/mcp/prompts.go
+++ b/mcp/prompts.go
@@ -47,6 +47,8 @@ type GetPromptResult struct {
 // that requires argument values to be provided when calling prompts/get.
 // If Arguments is nil or empty, this is a static prompt that takes no arguments.
 type Prompt struct {
+	// Meta is a metadata object that is reserved by MCP for storing additional information.
+	Meta *Meta `json:"_meta,omitempty"`
 	// The name of the prompt or prompt template.
 	Name string `json:"name"`
 	// An optional description of what this prompt provides
diff --git a/mcp/tools.go b/mcp/tools.go
index 3227a0f..493e8c7 100644
--- a/mcp/tools.go
+++ b/mcp/tools.go
@@ -7,6 +7,8 @@ import (
 	"net/http"
 	"reflect"
 	"strconv"
+
+	"github.com/invopop/jsonschema"
 )
 
 var errToolSchemaConflict = errors.New("provide either InputSchema or RawInputSchema, not both")
@@ -38,6 +40,10 @@ type ListToolsResult struct {
 type CallToolResult struct {
 	Result
 	Content []Content `json:"content"` // Can be TextContent, ImageContent, AudioContent, or EmbeddedResource
+	// Structured content returned as a JSON object in the structuredContent field of a result.
+	// For backwards compatibility, a tool that returns structured content SHOULD also return
+	// functionally equivalent unstructured content.
+	StructuredContent any `json:"structuredContent,omitempty"`
 	// Whether the tool call ended in an error.
 	//
 	// If not set, this is assumed to be false (the call was successful).
@@ -467,24 +473,29 @@ func (r CallToolRequest) RequireBoolSlice(key string) ([]bool, error) {
 // MarshalJSON implements custom JSON marshaling for CallToolResult
 func (r CallToolResult) MarshalJSON() ([]byte, error) {
 	m := make(map[string]any)
-	
+
 	// Marshal Meta if present
 	if r.Meta != nil {
 		m["_meta"] = r.Meta
 	}
-	
+
 	// Marshal Content array
 	content := make([]any, len(r.Content))
 	for i, c := range r.Content {
 		content[i] = c
 	}
 	m["content"] = content
-	
+
+	// Marshal StructuredContent if present
+	if r.StructuredContent != nil {
+		m["structuredContent"] = r.StructuredContent
+	}
+
 	// Marshal IsError if true
 	if r.IsError {
 		m["isError"] = r.IsError
 	}
-	
+
 	return json.Marshal(m)
 }
 
@@ -494,14 +505,14 @@ func (r *CallToolResult) UnmarshalJSON(data []byte) error {
 	if err := json.Unmarshal(data, &raw); err != nil {
 		return err
 	}
-	
+
 	// Unmarshal Meta
 	if meta, ok := raw["_meta"]; ok {
 		if metaMap, ok := meta.(map[string]any); ok {
-			r.Meta = metaMap
+			r.Meta = NewMetaFromMap(metaMap)
 		}
 	}
-	
+
 	// Unmarshal Content array
 	if contentRaw, ok := raw["content"]; ok {
 		if contentArray, ok := contentRaw.([]any); ok {
@@ -519,14 +530,19 @@ func (r *CallToolResult) UnmarshalJSON(data []byte) error {
 			}
 		}
 	}
-	
+
+	// Unmarshal StructuredContent if present
+	if structured, ok := raw["structuredContent"]; ok {
+		r.StructuredContent = structured
+	}
+
 	// Unmarshal IsError
 	if isError, ok := raw["isError"]; ok {
 		if isErrorBool, ok := isError.(bool); ok {
 			r.IsError = isErrorBool
 		}
 	}
-	
+
 	return nil
 }
 
@@ -539,6 +555,8 @@ type ToolListChangedNotification struct {
 
 // Tool represents the definition for a tool the client can call.
 type Tool struct {
+	// Meta is a metadata object that is reserved by MCP for storing additional information.
+	Meta *Meta `json:"_meta,omitempty"`
 	// The name of the tool.
 	Name string `json:"name"`
 	// A human-readable description of the tool.
@@ -547,6 +565,10 @@ type Tool struct {
 	InputSchema ToolInputSchema `json:"inputSchema"`
 	// Alternative to InputSchema - allows arbitrary JSON Schema to be provided
 	RawInputSchema json.RawMessage `json:"-"` // Hide this from JSON marshaling
+	// A JSON Schema object defining the expected output returned by the tool .
+	OutputSchema ToolOutputSchema `json:"outputSchema,omitempty"`
+	// Optional JSON Schema defining expected output structure
+	RawOutputSchema json.RawMessage `json:"-"` // Hide this from JSON marshaling
 	// Optional properties describing tool behavior
 	Annotations ToolAnnotation `json:"annotations"`
 }
@@ -560,7 +582,7 @@ func (t Tool) GetName() string {
 // It handles marshaling either InputSchema or RawInputSchema based on which is set.
 func (t Tool) MarshalJSON() ([]byte, error) {
 	// Create a map to build the JSON structure
-	m := make(map[string]any, 3)
+	m := make(map[string]any, 5)
 
 	// Add the name and description
 	m["name"] = t.Name
@@ -568,7 +590,7 @@ func (t Tool) MarshalJSON() ([]byte, error) {
 		m["description"] = t.Description
 	}
 
-	// Determine which schema to use
+	// Determine which input schema to use
 	if t.RawInputSchema != nil {
 		if t.InputSchema.Type != "" {
 			return nil, fmt.Errorf("tool %s has both InputSchema and RawInputSchema set: %w", t.Name, errToolSchemaConflict)
@@ -579,22 +601,41 @@ func (t Tool) MarshalJSON() ([]byte, error) {
 		m["inputSchema"] = t.InputSchema
 	}
 
+	// Add output schema if present
+	if t.RawOutputSchema != nil {
+		if t.OutputSchema.Type != "" {
+			return nil, fmt.Errorf("tool %s has both OutputSchema and RawOutputSchema set: %w", t.Name, errToolSchemaConflict)
+		}
+		m["outputSchema"] = t.RawOutputSchema
+	} else if t.OutputSchema.Type != "" { // If no output schema is specified, do not return anything
+		m["outputSchema"] = t.OutputSchema
+	}
+
 	m["annotations"] = t.Annotations
 
 	return json.Marshal(m)
 }
 
-type ToolInputSchema struct {
+// ToolArgumentsSchema represents a JSON Schema for tool arguments.
+type ToolArgumentsSchema struct {
+	Defs       map[string]any `json:"$defs,omitempty"`
 	Type       string         `json:"type"`
 	Properties map[string]any `json:"properties,omitempty"`
 	Required   []string       `json:"required,omitempty"`
 }
 
+type ToolInputSchema ToolArgumentsSchema // For retro-compatibility
+type ToolOutputSchema ToolArgumentsSchema
+
 // MarshalJSON implements the json.Marshaler interface for ToolInputSchema.
-func (tis ToolInputSchema) MarshalJSON() ([]byte, error) {
+func (tis ToolArgumentsSchema) MarshalJSON() ([]byte, error) {
 	m := make(map[string]any)
 	m["type"] = tis.Type
 
+	if tis.Defs != nil {
+		m["$defs"] = tis.Defs
+	}
+
 	// Marshal Properties to '{}' rather than `nil` when its length equals zero
 	if tis.Properties != nil {
 		m["properties"] = tis.Properties
@@ -684,6 +725,95 @@ func WithDescription(description string) ToolOption {
 	}
 }
 
+// WithInputSchema creates a ToolOption that sets the input schema for a tool.
+// It accepts any Go type, usually a struct, and automatically generates a JSON schema from it.
+func WithInputSchema[T any]() ToolOption {
+	return func(t *Tool) {
+		var zero T
+
+		// Generate schema using invopop/jsonschema library
+		// Configure reflector to generate clean, MCP-compatible schemas
+		reflector := jsonschema.Reflector{
+			DoNotReference:            true, // Removes $defs map, outputs entire structure inline
+			Anonymous:                 true, // Hides auto-generated Schema IDs
+			AllowAdditionalProperties: true, // Removes additionalProperties: false
+		}
+		schema := reflector.Reflect(zero)
+
+		// Clean up schema for MCP compliance
+		schema.Version = "" // Remove $schema field
+
+		// Convert to raw JSON for MCP
+		mcpSchema, err := json.Marshal(schema)
+		if err != nil {
+			// Skip and maintain backward compatibility
+			return
+		}
+
+		t.InputSchema.Type = ""
+		t.RawInputSchema = json.RawMessage(mcpSchema)
+	}
+}
+
+// WithRawInputSchema sets a raw JSON schema for the tool's input.
+// Use this when you need full control over the schema or when working with
+// complex schemas that can't be generated from Go types. The jsonschema library
+// can handle complex schemas and provides nice extension points, so be sure to
+// check that out before using this.
+func WithRawInputSchema(schema json.RawMessage) ToolOption {
+	return func(t *Tool) {
+		t.RawInputSchema = schema
+	}
+}
+
+// WithOutputSchema creates a ToolOption that sets the output schema for a tool.
+// It accepts any Go type, usually a struct, and automatically generates a JSON schema from it.
+func WithOutputSchema[T any]() ToolOption {
+	return func(t *Tool) {
+		var zero T
+
+		// Generate schema using invopop/jsonschema library
+		// Configure reflector to generate clean, MCP-compatible schemas
+		reflector := jsonschema.Reflector{
+			DoNotReference:            true, // Removes $defs map, outputs entire structure inline
+			Anonymous:                 true, // Hides auto-generated Schema IDs
+			AllowAdditionalProperties: true, // Removes additionalProperties: false
+		}
+		schema := reflector.Reflect(zero)
+
+		// Clean up schema for MCP compliance
+		schema.Version = "" // Remove $schema field
+
+		// Convert to raw JSON for MCP
+		mcpSchema, err := json.Marshal(schema)
+		if err != nil {
+			// Skip and maintain backward compatibility
+			return
+		}
+
+		// Retrieve the schema from raw JSON
+		if err := json.Unmarshal(mcpSchema, &t.OutputSchema); err != nil {
+			// Skip and maintain backward compatibility
+			return
+		}
+
+		// Always set the type to "object" as of the current MCP spec
+		// https://modelcontextprotocol.io/specification/2025-06-18/server/tools#output-schema
+		t.OutputSchema.Type = "object"
+	}
+}
+
+// WithRawOutputSchema sets a raw JSON schema for the tool's output.
+// Use this when you need full control over the schema or when working with
+// complex schemas that can't be generated from Go types. The jsonschema library
+// can handle complex schemas and provides nice extension points, so be sure to
+// check that out before using this.
+func WithRawOutputSchema(schema json.RawMessage) ToolOption {
+	return func(t *Tool) {
+		t.RawOutputSchema = schema
+	}
+}
+
 // WithToolAnnotation adds optional hints about the Tool.
 func WithToolAnnotation(annotation ToolAnnotation) ToolOption {
 	return func(t *Tool) {
diff --git a/mcp/typed_tools.go b/mcp/typed_tools.go
index 68d8cdd..a03a19d 100644
--- a/mcp/typed_tools.go
+++ b/mcp/typed_tools.go
@@ -8,6 +8,9 @@ import (
 // TypedToolHandlerFunc is a function that handles a tool call with typed arguments
 type TypedToolHandlerFunc[T any] func(ctx context.Context, request CallToolRequest, args T) (*CallToolResult, error)
 
+// StructuredToolHandlerFunc is a function that handles a tool call with typed arguments and returns structured output
+type StructuredToolHandlerFunc[TArgs any, TResult any] func(ctx context.Context, request CallToolRequest, args TArgs) (TResult, error)
+
 // NewTypedToolHandler creates a ToolHandlerFunc that automatically binds arguments to a typed struct
 func NewTypedToolHandler[T any](handler TypedToolHandlerFunc[T]) func(ctx context.Context, request CallToolRequest) (*CallToolResult, error) {
 	return func(ctx context.Context, request CallToolRequest) (*CallToolResult, error) {
@@ -18,3 +21,22 @@ func NewTypedToolHandler[T any](handler TypedToolHandlerFunc[T]) func(ctx contex
 		return handler(ctx, request, args)
 	}
 }
+
+// NewStructuredToolHandler creates a ToolHandlerFunc that automatically binds arguments to a typed struct
+// and returns structured output. It automatically creates both structured and
+// text content (from the structured output) for backwards compatibility.
+func NewStructuredToolHandler[TArgs any, TResult any](handler StructuredToolHandlerFunc[TArgs, TResult]) func(ctx context.Context, request CallToolRequest) (*CallToolResult, error) {
+	return func(ctx context.Context, request CallToolRequest) (*CallToolResult, error) {
+		var args TArgs
+		if err := request.BindArguments(&args); err != nil {
+			return NewToolResultError(fmt.Sprintf("failed to bind arguments: %v", err)), nil
+		}
+
+		result, err := handler(ctx, request, args)
+		if err != nil {
+			return NewToolResultError(fmt.Sprintf("tool execution failed: %v", err)), nil
+		}
+
+		return NewToolResultStructuredOnly(result), nil
+	}
+}
diff --git a/mcp/types.go b/mcp/types.go
index 909ebd8..f871b7d 100644
--- a/mcp/types.go
+++ b/mcp/types.go
@@ -8,8 +8,9 @@ import (
 	"maps"
 	"strconv"
 
-	"github.com/yosida95/uritemplate/v3"
 	"net/http"
+
+	"github.com/yosida95/uritemplate/v3"
 )
 
 type MCPMethod string
@@ -97,12 +98,13 @@ func (t *URITemplate) UnmarshalJSON(data []byte) error {
 type JSONRPCMessage any
 
 // LATEST_PROTOCOL_VERSION is the most recent version of the MCP protocol.
-const LATEST_PROTOCOL_VERSION = "2025-03-26"
+const LATEST_PROTOCOL_VERSION = "2025-06-18"
 
 // ValidProtocolVersions lists all known valid MCP protocol versions.
 var ValidProtocolVersions = []string{
-	"2024-11-05",
 	LATEST_PROTOCOL_VERSION,
+	"2025-03-26",
+	"2024-11-05",
 }
 
 // JSONRPC_VERSION is the version of JSON-RPC used by MCP.
@@ -151,6 +153,18 @@ func (m *Meta) UnmarshalJSON(data []byte) error {
 	return nil
 }
 
+func NewMetaFromMap(m map[string]any) *Meta {
+	progressToken := m["progressToken"]
+	if progressToken != nil {
+		delete(m, "progressToken")
+	}
+
+	return &Meta{
+		ProgressToken:    progressToken,
+		AdditionalFields: m,
+	}
+}
+
 type Request struct {
 	Method string        `json:"method"`
 	Params RequestParams `json:"params,omitempty"`
@@ -232,7 +246,7 @@ func (p *NotificationParams) UnmarshalJSON(data []byte) error {
 type Result struct {
 	// This result property is reserved by the protocol to allow clients and
 	// servers to attach additional metadata to their responses.
-	Meta map[string]any `json:"_meta,omitempty"`
+	Meta *Meta `json:"_meta,omitempty"`
 }
 
 // RequestId is a uniquely identifying ID for a request in JSON-RPC.
@@ -471,6 +485,8 @@ type ServerCapabilities struct {
 		// list.
 		ListChanged bool `json:"listChanged,omitempty"`
 	} `json:"resources,omitempty"`
+	// Present if the server supports sending sampling requests to clients.
+	Sampling *struct{} `json:"sampling,omitempty"`
 	// Present if the server offers any tools to call.
 	Tools *struct {
 		// Whether this server supports notifications for changes to the tool list.
@@ -643,6 +659,8 @@ type ResourceUpdatedNotificationParams struct {
 // Resource represents a known resource that the server is capable of reading.
 type Resource struct {
 	Annotated
+	// Meta is a metadata object that is reserved by MCP for storing additional information.
+	Meta *Meta `json:"_meta,omitempty"`
 	// The URI of this resource.
 	URI string `json:"uri"`
 	// A human-readable name for this resource.
@@ -667,6 +685,8 @@ func (r Resource) GetName() string {
 // on the server.
 type ResourceTemplate struct {
 	Annotated
+	// Meta is a metadata object that is reserved by MCP for storing additional information.
+	Meta *Meta `json:"_meta,omitempty"`
 	// A URI template (according to RFC 6570) that can be used to construct
 	// resource URIs.
 	URITemplate *URITemplate `json:"uriTemplate"`
@@ -696,6 +716,8 @@ type ResourceContents interface {
 }
 
 type TextResourceContents struct {
+	// Meta is a metadata object that is reserved by MCP for storing additional information.
+	Meta *Meta `json:"_meta,omitempty"`
 	// The URI of this resource.
 	URI string `json:"uri"`
 	// The MIME type of this resource, if known.
@@ -708,6 +730,8 @@ type TextResourceContents struct {
 func (TextResourceContents) isResourceContents() {}
 
 type BlobResourceContents struct {
+	// Meta is a metadata object that is reserved by MCP for storing additional information.
+	Meta *Meta `json:"_meta,omitempty"`
 	// The URI of this resource.
 	URI string `json:"uri"`
 	// The MIME type of this resource, if known.
@@ -866,6 +890,8 @@ type Content interface {
 // It must have Type set to "text".
 type TextContent struct {
 	Annotated
+	// Meta is a metadata object that is reserved by MCP for storing additional information.
+	Meta *Meta  `json:"_meta,omitempty"`
 	Type string `json:"type"` // Must be "text"
 	// The text content of the message.
 	Text string `json:"text"`
@@ -877,6 +903,8 @@ func (TextContent) isContent() {}
 // It must have Type set to "image".
 type ImageContent struct {
 	Annotated
+	// Meta is a metadata object that is reserved by MCP for storing additional information.
+	Meta *Meta  `json:"_meta,omitempty"`
 	Type string `json:"type"` // Must be "image"
 	// The base64-encoded image data.
 	Data string `json:"data"`
@@ -890,6 +918,8 @@ func (ImageContent) isContent() {}
 // It must have Type set to "audio".
 type AudioContent struct {
 	Annotated
+	// Meta is a metadata object that is reserved by MCP for storing additional information.
+	Meta *Meta  `json:"_meta,omitempty"`
 	Type string `json:"type"` // Must be "audio"
 	// The base64-encoded audio data.
 	Data string `json:"data"`
@@ -921,6 +951,8 @@ func (ResourceLink) isContent() {}
 // benefit of the LLM and/or the user.
 type EmbeddedResource struct {
 	Annotated
+	// Meta is a metadata object that is reserved by MCP for storing additional information.
+	Meta     *Meta            `json:"_meta,omitempty"`
 	Type     string           `json:"type"`
 	Resource ResourceContents `json:"resource"`
 }
@@ -1055,6 +1087,8 @@ type ListRootsResult struct {
 
 // Root represents a root directory or file that the server can operate on.
 type Root struct {
+	// Meta is a metadata object that is reserved by MCP for storing additional information.
+	Meta *Meta `json:"_meta,omitempty"`
 	// The URI identifying the root. This *must* start with file:// for now.
 	// This restriction may be relaxed in future versions of the protocol to allow
 	// other URI schemes.
@@ -1113,23 +1147,23 @@ func UnmarshalContent(data []byte) (Content, error) {
 	}
 
 	switch contentType {
-	case "text":
+	case ContentTypeText:
 		var content TextContent
 		err := json.Unmarshal(data, &content)
 		return content, err
-	case "image":
+	case ContentTypeImage:
 		var content ImageContent
 		err := json.Unmarshal(data, &content)
 		return content, err
-	case "audio":
+	case ContentTypeAudio:
 		var content AudioContent
 		err := json.Unmarshal(data, &content)
 		return content, err
-	case "resource_link":
+	case ContentTypeLink:
 		var content ResourceLink
 		err := json.Unmarshal(data, &content)
 		return content, err
-	case "resource":
+	case ContentTypeResource:
 		var content EmbeddedResource
 		err := json.Unmarshal(data, &content)
 		return content, err
diff --git a/mcp/utils.go b/mcp/utils.go
index 3e652ef..19cfa89 100644
--- a/mcp/utils.go
+++ b/mcp/utils.go
@@ -8,54 +8,54 @@ import (
 )
 
 // ClientRequest types
-var _ ClientRequest = &PingRequest{}
-var _ ClientRequest = &InitializeRequest{}
-var _ ClientRequest = &CompleteRequest{}
-var _ ClientRequest = &SetLevelRequest{}
-var _ ClientRequest = &GetPromptRequest{}
-var _ ClientRequest = &ListPromptsRequest{}
-var _ ClientRequest = &ListResourcesRequest{}
-var _ ClientRequest = &ReadResourceRequest{}
-var _ ClientRequest = &SubscribeRequest{}
-var _ ClientRequest = &UnsubscribeRequest{}
-var _ ClientRequest = &CallToolRequest{}
-var _ ClientRequest = &ListToolsRequest{}
+var _ ClientRequest = (*PingRequest)(nil)
+var _ ClientRequest = (*InitializeRequest)(nil)
+var _ ClientRequest = (*CompleteRequest)(nil)
+var _ ClientRequest = (*SetLevelRequest)(nil)
+var _ ClientRequest = (*GetPromptRequest)(nil)
+var _ ClientRequest = (*ListPromptsRequest)(nil)
+var _ ClientRequest = (*ListResourcesRequest)(nil)
+var _ ClientRequest = (*ReadResourceRequest)(nil)
+var _ ClientRequest = (*SubscribeRequest)(nil)
+var _ ClientRequest = (*UnsubscribeRequest)(nil)
+var _ ClientRequest = (*CallToolRequest)(nil)
+var _ ClientRequest = (*ListToolsRequest)(nil)
 
 // ClientNotification types
-var _ ClientNotification = &CancelledNotification{}
-var _ ClientNotification = &ProgressNotification{}
-var _ ClientNotification = &InitializedNotification{}
-var _ ClientNotification = &RootsListChangedNotification{}
+var _ ClientNotification = (*CancelledNotification)(nil)
+var _ ClientNotification = (*ProgressNotification)(nil)
+var _ ClientNotification = (*InitializedNotification)(nil)
+var _ ClientNotification = (*RootsListChangedNotification)(nil)
 
 // ClientResult types
-var _ ClientResult = &EmptyResult{}
-var _ ClientResult = &CreateMessageResult{}
-var _ ClientResult = &ListRootsResult{}
+var _ ClientResult = (*EmptyResult)(nil)
+var _ ClientResult = (*CreateMessageResult)(nil)
+var _ ClientResult = (*ListRootsResult)(nil)
 
 // ServerRequest types
-var _ ServerRequest = &PingRequest{}
-var _ ServerRequest = &CreateMessageRequest{}
-var _ ServerRequest = &ListRootsRequest{}
+var _ ServerRequest = (*PingRequest)(nil)
+var _ ServerRequest = (*CreateMessageRequest)(nil)
+var _ ServerRequest = (*ListRootsRequest)(nil)
 
 // ServerNotification types
-var _ ServerNotification = &CancelledNotification{}
-var _ ServerNotification = &ProgressNotification{}
-var _ ServerNotification = &LoggingMessageNotification{}
-var _ ServerNotification = &ResourceUpdatedNotification{}
-var _ ServerNotification = &ResourceListChangedNotification{}
-var _ ServerNotification = &ToolListChangedNotification{}
-var _ ServerNotification = &PromptListChangedNotification{}
+var _ ServerNotification = (*CancelledNotification)(nil)
+var _ ServerNotification = (*ProgressNotification)(nil)
+var _ ServerNotification = (*LoggingMessageNotification)(nil)
+var _ ServerNotification = (*ResourceUpdatedNotification)(nil)
+var _ ServerNotification = (*ResourceListChangedNotification)(nil)
+var _ ServerNotification = (*ToolListChangedNotification)(nil)
+var _ ServerNotification = (*PromptListChangedNotification)(nil)
 
 // ServerResult types
-var _ ServerResult = &EmptyResult{}
-var _ ServerResult = &InitializeResult{}
-var _ ServerResult = &CompleteResult{}
-var _ ServerResult = &GetPromptResult{}
-var _ ServerResult = &ListPromptsResult{}
-var _ ServerResult = &ListResourcesResult{}
-var _ ServerResult = &ReadResourceResult{}
-var _ ServerResult = &CallToolResult{}
-var _ ServerResult = &ListToolsResult{}
+var _ ServerResult = (*EmptyResult)(nil)
+var _ ServerResult = (*InitializeResult)(nil)
+var _ ServerResult = (*CompleteResult)(nil)
+var _ ServerResult = (*GetPromptResult)(nil)
+var _ ServerResult = (*ListPromptsResult)(nil)
+var _ ServerResult = (*ListResourcesResult)(nil)
+var _ ServerResult = (*ReadResourceResult)(nil)
+var _ ServerResult = (*CallToolResult)(nil)
+var _ ServerResult = (*ListToolsResult)(nil)
 
 // Helper functions for type assertions
 
@@ -198,7 +198,7 @@ func NewPromptMessage(role Role, content Content) PromptMessage {
 // Helper function to create a new TextContent
 func NewTextContent(text string) TextContent {
 	return TextContent{
-		Type: "text",
+		Type: ContentTypeText,
 		Text: text,
 	}
 }
@@ -207,7 +207,7 @@ func NewTextContent(text string) TextContent {
 // Helper function to create a new ImageContent
 func NewImageContent(data, mimeType string) ImageContent {
 	return ImageContent{
-		Type:     "image",
+		Type:     ContentTypeImage,
 		Data:     data,
 		MIMEType: mimeType,
 	}
@@ -216,7 +216,7 @@ func NewImageContent(data, mimeType string) ImageContent {
 // Helper function to create a new AudioContent
 func NewAudioContent(data, mimeType string) AudioContent {
 	return AudioContent{
-		Type:     "audio",
+		Type:     ContentTypeAudio,
 		Data:     data,
 		MIMEType: mimeType,
 	}
@@ -225,7 +225,7 @@ func NewAudioContent(data, mimeType string) AudioContent {
 // Helper function to create a new ResourceLink
 func NewResourceLink(uri, name, description, mimeType string) ResourceLink {
 	return ResourceLink{
-		Type:        "resource_link",
+		Type:        ContentTypeLink,
 		URI:         uri,
 		Name:        name,
 		Description: description,
@@ -236,7 +236,7 @@ func NewResourceLink(uri, name, description, mimeType string) ResourceLink {
 // Helper function to create a new EmbeddedResource
 func NewEmbeddedResource(resource ResourceContents) EmbeddedResource {
 	return EmbeddedResource{
-		Type:     "resource",
+		Type:     ContentTypeResource,
 		Resource: resource,
 	}
 }
@@ -246,23 +246,79 @@ func NewToolResultText(text string) *CallToolResult {
 	return &CallToolResult{
 		Content: []Content{
 			TextContent{
-				Type: "text",
+				Type: ContentTypeText,
 				Text: text,
 			},
 		},
 	}
 }
 
+// NewToolResultJSON creates a new CallToolResult with a JSON content.
+func NewToolResultJSON[T any](data T) (*CallToolResult, error) {
+	b, err := json.Marshal(data)
+	if err != nil {
+		return nil, fmt.Errorf("unable to marshal JSON: %w", err)
+	}
+
+	return &CallToolResult{
+		Content: []Content{
+			TextContent{
+				Type: ContentTypeText,
+				Text: string(b),
+			},
+		},
+		StructuredContent: data,
+	}, nil
+}
+
+// NewToolResultStructured creates a new CallToolResult with structured content.
+// It includes both the structured content and a text representation for backward compatibility.
+func NewToolResultStructured(structured any, fallbackText string) *CallToolResult {
+	return &CallToolResult{
+		Content: []Content{
+			TextContent{
+				Type: "text",
+				Text: fallbackText,
+			},
+		},
+		StructuredContent: structured,
+	}
+}
+
+// NewToolResultStructuredOnly creates a new CallToolResult with structured
+// content and creates a JSON string fallback for backwards compatibility.
+// This is useful when you want to provide structured data without any specific text fallback.
+func NewToolResultStructuredOnly(structured any) *CallToolResult {
+	var fallbackText string
+	// Convert to JSON string for backward compatibility
+	jsonBytes, err := json.Marshal(structured)
+	if err != nil {
+		fallbackText = fmt.Sprintf("Error serializing structured content: %v", err)
+	} else {
+		fallbackText = string(jsonBytes)
+	}
+
+	return &CallToolResult{
+		Content: []Content{
+			TextContent{
+				Type: "text",
+				Text: fallbackText,
+			},
+		},
+		StructuredContent: structured,
+	}
+}
+
 // NewToolResultImage creates a new CallToolResult with both text and image content
 func NewToolResultImage(text, imageData, mimeType string) *CallToolResult {
 	return &CallToolResult{
 		Content: []Content{
 			TextContent{
-				Type: "text",
+				Type: ContentTypeText,
 				Text: text,
 			},
 			ImageContent{
-				Type:     "image",
+				Type:     ContentTypeImage,
 				Data:     imageData,
 				MIMEType: mimeType,
 			},
@@ -275,11 +331,11 @@ func NewToolResultAudio(text, imageData, mimeType string) *CallToolResult {
 	return &CallToolResult{
 		Content: []Content{
 			TextContent{
-				Type: "text",
+				Type: ContentTypeText,
 				Text: text,
 			},
 			AudioContent{
-				Type:     "audio",
+				Type:     ContentTypeAudio,
 				Data:     imageData,
 				MIMEType: mimeType,
 			},
@@ -295,11 +351,11 @@ func NewToolResultResource(
 	return &CallToolResult{
 		Content: []Content{
 			TextContent{
-				Type: "text",
+				Type: ContentTypeText,
 				Text: text,
 			},
 			EmbeddedResource{
-				Type:     "resource",
+				Type:     ContentTypeResource,
 				Resource: resource,
 			},
 		},
@@ -312,7 +368,7 @@ func NewToolResultError(text string) *CallToolResult {
 	return &CallToolResult{
 		Content: []Content{
 			TextContent{
-				Type: "text",
+				Type: ContentTypeText,
 				Text: text,
 			},
 		},
@@ -330,7 +386,7 @@ func NewToolResultErrorFromErr(text string, err error) *CallToolResult {
 	return &CallToolResult{
 		Content: []Content{
 			TextContent{
-				Type: "text",
+				Type: ContentTypeText,
 				Text: text,
 			},
 		},
@@ -345,7 +401,7 @@ func NewToolResultErrorf(format string, a ...any) *CallToolResult {
 	return &CallToolResult{
 		Content: []Content{
 			TextContent{
-				Type: "text",
+				Type: ContentTypeText,
 				Text: fmt.Sprintf(format, a...),
 			},
 		},
@@ -467,11 +523,11 @@ func ParseContent(contentMap map[string]any) (Content, error) {
 	contentType := ExtractString(contentMap, "type")
 
 	switch contentType {
-	case "text":
+	case ContentTypeText:
 		text := ExtractString(contentMap, "text")
 		return NewTextContent(text), nil
 
-	case "image":
+	case ContentTypeImage:
 		data := ExtractString(contentMap, "data")
 		mimeType := ExtractString(contentMap, "mimeType")
 		if data == "" || mimeType == "" {
@@ -479,7 +535,7 @@ func ParseContent(contentMap map[string]any) (Content, error) {
 		}
 		return NewImageContent(data, mimeType), nil
 
-	case "audio":
+	case ContentTypeAudio:
 		data := ExtractString(contentMap, "data")
 		mimeType := ExtractString(contentMap, "mimeType")
 		if data == "" || mimeType == "" {
@@ -487,7 +543,7 @@ func ParseContent(contentMap map[string]any) (Content, error) {
 		}
 		return NewAudioContent(data, mimeType), nil
 
-	case "resource_link":
+	case ContentTypeLink:
 		uri := ExtractString(contentMap, "uri")
 		name := ExtractString(contentMap, "name")
 		description := ExtractString(contentMap, "description")
@@ -497,7 +553,7 @@ func ParseContent(contentMap map[string]any) (Content, error) {
 		}
 		return NewResourceLink(uri, name, description, mimeType), nil
 
-	case "resource":
+	case ContentTypeResource:
 		resourceMap := ExtractMap(contentMap, "resource")
 		if resourceMap == nil {
 			return nil, fmt.Errorf("resource is missing")
@@ -529,7 +585,7 @@ func ParseGetPromptResult(rawMessage *json.RawMessage) (*GetPromptResult, error)
 	meta, ok := jsonContent["_meta"]
 	if ok {
 		if metaMap, ok := meta.(map[string]any); ok {
-			result.Meta = metaMap
+			result.Meta = NewMetaFromMap(metaMap)
 		}
 	}
 
@@ -595,7 +651,7 @@ func ParseCallToolResult(rawMessage *json.RawMessage) (*CallToolResult, error) {
 	meta, ok := jsonContent["_meta"]
 	if ok {
 		if metaMap, ok := meta.(map[string]any); ok {
-			result.Meta = metaMap
+			result.Meta = NewMetaFromMap(metaMap)
 		}
 	}
 
@@ -632,6 +688,12 @@ func ParseCallToolResult(rawMessage *json.RawMessage) (*CallToolResult, error) {
 		result.Content = append(result.Content, content)
 	}
 
+	// Handle structured content
+	structuredContent, ok := jsonContent["structuredContent"]
+	if ok {
+		result.StructuredContent = structuredContent
+	}
+
 	return &result, nil
 }
 
@@ -677,7 +739,7 @@ func ParseReadResourceResult(rawMessage *json.RawMessage) (*ReadResourceResult,
 	meta, ok := jsonContent["_meta"]
 	if ok {
 		if metaMap, ok := meta.(map[string]any); ok {
-			result.Meta = metaMap
+			result.Meta = NewMetaFromMap(metaMap)
 		}
 	}
 
diff --git a/sampling_server b/sampling_server
deleted file mode 100755
index ce83fb4..0000000
Binary files a/sampling_server and /dev/null differ
diff --git a/server/constants.go b/server/constants.go
new file mode 100644
index 0000000..e071b2e
--- /dev/null
+++ b/server/constants.go
@@ -0,0 +1,7 @@
+package server
+
+// Common HTTP header constants used across server transports
+const (
+	HeaderKeySessionID       = "Mcp-Session-Id"
+	HeaderKeyProtocolVersion = "Mcp-Protocol-Version"
+)
diff --git a/server/errors.go b/server/errors.go
index ecbe91e..3864f36 100644
--- a/server/errors.go
+++ b/server/errors.go
@@ -21,7 +21,7 @@ var (
 
 	// Notification-related errors
 	ErrNotificationNotInitialized = errors.New("notification channel not initialized")
-	ErrNotificationChannelBlocked = errors.New("notification channel full or blocked")
+	ErrNotificationChannelBlocked = errors.New("notification channel queue is full - client may not be processing notifications fast enough")
 )
 
 // ErrDynamicPathConfig is returned when attempting to use static path methods with dynamic path configuration
diff --git a/server/sampling.go b/server/sampling.go
index ae0812f..2118db1 100644
--- a/server/sampling.go
+++ b/server/sampling.go
@@ -12,6 +12,9 @@ import (
 func (s *MCPServer) EnableSampling() {
 	s.capabilitiesMu.Lock()
 	defer s.capabilitiesMu.Unlock()
+
+	enabled := true
+	s.capabilities.sampling = &enabled
 }
 
 // RequestSampling sends a sampling request to the client.
diff --git a/server/server.go b/server/server.go
index 896eb9b..b9fb361 100644
--- a/server/server.go
+++ b/server/server.go
@@ -43,6 +43,9 @@ type ToolHandlerFunc func(ctx context.Context, request mcp.CallToolRequest) (*mc
 // ToolHandlerMiddleware is a middleware function that wraps a ToolHandlerFunc.
 type ToolHandlerMiddleware func(ToolHandlerFunc) ToolHandlerFunc
 
+// ResourceHandlerMiddleware is a middleware function that wraps a ResourceHandlerFunc.
+type ResourceHandlerMiddleware func(ResourceHandlerFunc) ResourceHandlerFunc
+
 // ToolFilterFunc is a function that filters tools based on context, typically using session information.
 type ToolFilterFunc func(ctx context.Context, tools []mcp.Tool) []mcp.Tool
 
@@ -144,28 +147,30 @@ type NotificationHandlerFunc func(ctx context.Context, notification mcp.JSONRPCN
 type MCPServer struct {
 	// Separate mutexes for different resource types
 	resourcesMu            sync.RWMutex
+	resourceMiddlewareMu   sync.RWMutex
 	promptsMu              sync.RWMutex
 	toolsMu                sync.RWMutex
-	middlewareMu           sync.RWMutex
+	toolMiddlewareMu       sync.RWMutex
 	notificationHandlersMu sync.RWMutex
 	capabilitiesMu         sync.RWMutex
 	toolFiltersMu          sync.RWMutex
 
-	name                   string
-	version                string
-	instructions           string
-	resources              map[string]resourceEntry
-	resourceTemplates      map[string]resourceTemplateEntry
-	prompts                map[string]mcp.Prompt
-	promptHandlers         map[string]PromptHandlerFunc
-	tools                  map[string]ServerTool
-	toolHandlerMiddlewares []ToolHandlerMiddleware
-	toolFilters            []ToolFilterFunc
-	notificationHandlers   map[string]NotificationHandlerFunc
-	capabilities           serverCapabilities
-	paginationLimit        *int
-	sessions               sync.Map
-	hooks                  *Hooks
+	name                       string
+	version                    string
+	instructions               string
+	resources                  map[string]resourceEntry
+	resourceTemplates          map[string]resourceTemplateEntry
+	prompts                    map[string]mcp.Prompt
+	promptHandlers             map[string]PromptHandlerFunc
+	tools                      map[string]ServerTool
+	toolHandlerMiddlewares     []ToolHandlerMiddleware
+	resourceHandlerMiddlewares []ResourceHandlerMiddleware
+	toolFilters                []ToolFilterFunc
+	notificationHandlers       map[string]NotificationHandlerFunc
+	capabilities               serverCapabilities
+	paginationLimit            *int
+	sessions                   sync.Map
+	hooks                      *Hooks
 }
 
 // WithPaginationLimit sets the pagination limit for the server.
@@ -181,6 +186,7 @@ type serverCapabilities struct {
 	resources *resourceCapabilities
 	prompts   *promptCapabilities
 	logging   *bool
+	sampling  *bool
 }
 
 // resourceCapabilities defines the supported resource-related features
@@ -216,12 +222,42 @@ func WithToolHandlerMiddleware(
 	toolHandlerMiddleware ToolHandlerMiddleware,
 ) ServerOption {
 	return func(s *MCPServer) {
-		s.middlewareMu.Lock()
+		s.toolMiddlewareMu.Lock()
 		s.toolHandlerMiddlewares = append(s.toolHandlerMiddlewares, toolHandlerMiddleware)
-		s.middlewareMu.Unlock()
+		s.toolMiddlewareMu.Unlock()
+	}
+}
+
+// WithResourceHandlerMiddleware allows adding a middleware for the
+// resource handler call chain.
+func WithResourceHandlerMiddleware(
+	resourceHandlerMiddleware ResourceHandlerMiddleware,
+) ServerOption {
+	return func(s *MCPServer) {
+		s.resourceMiddlewareMu.Lock()
+		s.resourceHandlerMiddlewares = append(s.resourceHandlerMiddlewares, resourceHandlerMiddleware)
+		s.resourceMiddlewareMu.Unlock()
 	}
 }
 
+// WithResourceRecovery adds a middleware that recovers from panics in resource handlers.
+func WithResourceRecovery() ServerOption {
+	return WithResourceHandlerMiddleware(func(next ResourceHandlerFunc) ResourceHandlerFunc {
+		return func(ctx context.Context, request mcp.ReadResourceRequest) (result []mcp.ResourceContents, err error) {
+			defer func() {
+				if r := recover(); r != nil {
+					err = fmt.Errorf(
+						"panic recovered in %s resource handler: %v",
+						request.Params.URI,
+						r,
+					)
+				}
+			}()
+			return next(ctx, request)
+		}
+	})
+}
+
 // WithToolFilter adds a filter function that will be applied to tools before they are returned in list_tools
 func WithToolFilter(
 	toolFilter ToolFilterFunc,
@@ -300,14 +336,16 @@ func NewMCPServer(
 	opts ...ServerOption,
 ) *MCPServer {
 	s := &MCPServer{
-		resources:            make(map[string]resourceEntry),
-		resourceTemplates:    make(map[string]resourceTemplateEntry),
-		prompts:              make(map[string]mcp.Prompt),
-		promptHandlers:       make(map[string]PromptHandlerFunc),
-		tools:                make(map[string]ServerTool),
-		name:                 name,
-		version:              version,
-		notificationHandlers: make(map[string]NotificationHandlerFunc),
+		resources:                  make(map[string]resourceEntry),
+		resourceTemplates:          make(map[string]resourceTemplateEntry),
+		prompts:                    make(map[string]mcp.Prompt),
+		promptHandlers:             make(map[string]PromptHandlerFunc),
+		tools:                      make(map[string]ServerTool),
+		toolHandlerMiddlewares:     make([]ToolHandlerMiddleware, 0),
+		resourceHandlerMiddlewares: make([]ResourceHandlerMiddleware, 0),
+		name:                       name,
+		version:                    version,
+		notificationHandlers:       make(map[string]NotificationHandlerFunc),
 		capabilities: serverCapabilities{
 			tools:     nil,
 			resources: nil,
@@ -348,6 +386,14 @@ func (s *MCPServer) AddResources(resources ...ServerResource) {
 	}
 }
 
+// SetResources replaces all existing resources with the provided list
+func (s *MCPServer) SetResources(resources ...ServerResource) {
+	s.resourcesMu.Lock()
+	s.resources = make(map[string]resourceEntry, len(resources))
+	s.resourcesMu.Unlock()
+	s.AddResources(resources...)
+}
+
 // AddResource registers a new resource and its handler
 func (s *MCPServer) AddResource(
 	resource mcp.Resource,
@@ -356,6 +402,24 @@ func (s *MCPServer) AddResource(
 	s.AddResources(ServerResource{Resource: resource, Handler: handler})
 }
 
+// DeleteResources removes resources from the server
+func (s *MCPServer) DeleteResources(uris ...string) {
+	s.resourcesMu.Lock()
+	var exists bool
+	for _, uri := range uris {
+		if _, ok := s.resources[uri]; ok {
+			delete(s.resources, uri)
+			exists = true
+		}
+	}
+	s.resourcesMu.Unlock()
+
+	// Send notification to all initialized sessions if listChanged capability is enabled and we actually remove a resource
+	if exists && s.capabilities.resources != nil && s.capabilities.resources.listChanged {
+		s.SendNotificationToAllClients(mcp.MethodNotificationResourcesListChanged, nil)
+	}
+}
+
 // RemoveResource removes a resource from the server
 func (s *MCPServer) RemoveResource(uri string) {
 	s.resourcesMu.Lock()
@@ -391,6 +455,14 @@ func (s *MCPServer) AddResourceTemplates(resourceTemplates ...ServerResourceTemp
 	}
 }
 
+// SetResourceTemplates replaces all existing resource templates with the provided list
+func (s *MCPServer) SetResourceTemplates(templates ...ServerResourceTemplate) {
+	s.resourcesMu.Lock()
+	s.resourceTemplates = make(map[string]resourceTemplateEntry, len(templates))
+	s.resourcesMu.Unlock()
+	s.AddResourceTemplates(templates...)
+}
+
 // AddResourceTemplate registers a new resource template and its handler
 func (s *MCPServer) AddResourceTemplate(
 	template mcp.ResourceTemplate,
@@ -422,6 +494,15 @@ func (s *MCPServer) AddPrompt(prompt mcp.Prompt, handler PromptHandlerFunc) {
 	s.AddPrompts(ServerPrompt{Prompt: prompt, Handler: handler})
 }
 
+// SetPrompts replaces all existing prompts with the provided list
+func (s *MCPServer) SetPrompts(prompts ...ServerPrompt) {
+	s.promptsMu.Lock()
+	s.prompts = make(map[string]mcp.Prompt, len(prompts))
+	s.promptHandlers = make(map[string]PromptHandlerFunc, len(prompts))
+	s.promptsMu.Unlock()
+	s.AddPrompts(prompts...)
+}
+
 // DeletePrompts removes prompts from the server
 func (s *MCPServer) DeletePrompts(names ...string) {
 	s.promptsMu.Lock()
@@ -511,6 +592,30 @@ func (s *MCPServer) SetTools(tools ...ServerTool) {
 	s.AddTools(tools...)
 }
 
+// GetTool retrieves the specified tool
+func (s *MCPServer) GetTool(toolName string) *ServerTool {
+	s.toolsMu.RLock()
+	defer s.toolsMu.RUnlock()
+	if tool, ok := s.tools[toolName]; ok {
+		return &tool
+	}
+	return nil
+}
+
+func (s *MCPServer) ListTools() map[string]*ServerTool {
+	s.toolsMu.RLock()
+	defer s.toolsMu.RUnlock()
+	if len(s.tools) == 0 {
+		return nil
+	}
+	// Create a copy to prevent external modification
+	toolsCopy := make(map[string]*ServerTool, len(s.tools))
+	for name, tool := range s.tools {
+		toolsCopy[name] = &tool
+	}
+	return toolsCopy
+}
+
 // DeleteTools removes tools from the server
 func (s *MCPServer) DeleteTools(names ...string) {
 	s.toolsMu.Lock()
@@ -580,6 +685,10 @@ func (s *MCPServer) handleInitialize(
 		capabilities.Logging = &struct{}{}
 	}
 
+	if s.capabilities.sampling != nil && *s.capabilities.sampling {
+		capabilities.Sampling = &struct{}{}
+	}
+
 	result := mcp.InitializeResult{
 		ProtocolVersion: s.protocolVersion(request.Params.ProtocolVersion),
 		ServerInfo: mcp.Implementation{
@@ -604,6 +713,14 @@ func (s *MCPServer) handleInitialize(
 }
 
 func (s *MCPServer) protocolVersion(clientVersion string) string {
+	// For backwards compatibility, if the server does not receive an MCP-Protocol-Version header,
+	// and has no other way to identify the version - for example, by relying on the protocol version negotiated
+	// during initialization - the server SHOULD assume protocol version 2025-03-26
+	// https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#protocol-version-header
+	if len(clientVersion) == 0 {
+		clientVersion = "2025-03-26"
+	}
+
 	if slices.Contains(mcp.ValidProtocolVersions, clientVersion) {
 		return clientVersion
 	}
@@ -782,7 +899,17 @@ func (s *MCPServer) handleReadResource(
 	if entry, ok := s.resources[request.Params.URI]; ok {
 		handler := entry.handler
 		s.resourcesMu.RUnlock()
-		contents, err := handler(ctx, request)
+
+		finalHandler := handler
+		s.resourceMiddlewareMu.RLock()
+		mw := s.resourceHandlerMiddlewares
+		// Apply middlewares in reverse order
+		for i := len(mw) - 1; i >= 0; i-- {
+			finalHandler = mw[i](finalHandler)
+		}
+		s.resourceMiddlewareMu.RUnlock()
+
+		contents, err := finalHandler(ctx, request)
 		if err != nil {
 			return nil, &requestError{
 				id:   id,
@@ -1036,14 +1163,14 @@ func (s *MCPServer) handleToolCall(
 
 	finalHandler := tool.Handler
 
-	s.middlewareMu.RLock()
+	s.toolMiddlewareMu.RLock()
 	mw := s.toolHandlerMiddlewares
-	s.middlewareMu.RUnlock()
 
 	// Apply middlewares in reverse order
 	for i := len(mw) - 1; i >= 0; i-- {
 		finalHandler = mw[i](finalHandler)
 	}
+	s.toolMiddlewareMu.RUnlock()
 
 	result, err := finalHandler(ctx, request)
 	if err != nil {
diff --git a/server/stdio.go b/server/stdio.go
index 4d567d8..8c270e1 100644
--- a/server/stdio.go
+++ b/server/stdio.go
@@ -29,6 +29,20 @@ type StdioServer struct {
 	server      *MCPServer
 	errLogger   *log.Logger
 	contextFunc StdioContextFunc
+
+	// Thread-safe tool call processing
+	toolCallQueue  chan *toolCallWork
+	workerWg       sync.WaitGroup
+	workerPoolSize int
+	queueSize      int
+	writeMu        sync.Mutex // Protects concurrent writes
+}
+
+// toolCallWork represents a queued tool call request
+type toolCallWork struct {
+	ctx     context.Context
+	message json.RawMessage
+	writer  io.Writer
 }
 
 // StdioOption defines a function type for configuring StdioServer
@@ -50,6 +64,32 @@ func WithStdioContextFunc(fn StdioContextFunc) StdioOption {
 	}
 }
 
+// WithWorkerPoolSize sets the number of workers for processing tool calls
+func WithWorkerPoolSize(size int) StdioOption {
+	return func(s *StdioServer) {
+		const maxWorkerPoolSize = 100
+		if size > 0 && size <= maxWorkerPoolSize {
+			s.workerPoolSize = size
+		} else if size > maxWorkerPoolSize {
+			s.errLogger.Printf("Worker pool size %d exceeds maximum (%d), using maximum", size, maxWorkerPoolSize)
+			s.workerPoolSize = maxWorkerPoolSize
+		}
+	}
+}
+
+// WithQueueSize sets the size of the tool call queue
+func WithQueueSize(size int) StdioOption {
+	return func(s *StdioServer) {
+		const maxQueueSize = 10000
+		if size > 0 && size <= maxQueueSize {
+			s.queueSize = size
+		} else if size > maxQueueSize {
+			s.errLogger.Printf("Queue size %d exceeds maximum (%d), using maximum", size, maxQueueSize)
+			s.queueSize = maxQueueSize
+		}
+	}
+}
+
 // stdioSession is a static client session, since stdio has only one client.
 type stdioSession struct {
 	notifications      chan mcp.JSONRPCNotification
@@ -218,6 +258,8 @@ func NewStdioServer(server *MCPServer) *StdioServer {
 			"",
 			log.LstdFlags,
 		), // Default to discarding logs
+		workerPoolSize: 5,   // Default worker pool size
+		queueSize:      100, // Default queue size
 	}
 }
 
@@ -281,6 +323,30 @@ func (s *StdioServer) processInputStream(ctx context.Context, reader *bufio.Read
 	}
 }
 
+// toolCallWorker processes tool calls from the queue
+func (s *StdioServer) toolCallWorker(ctx context.Context) {
+	defer s.workerWg.Done()
+
+	for {
+		select {
+		case work, ok := <-s.toolCallQueue:
+			if !ok {
+				// Channel closed, exit worker
+				return
+			}
+			// Process the tool call
+			response := s.server.HandleMessage(work.ctx, work.message)
+			if response != nil {
+				if err := s.writeResponse(response, work.writer); err != nil {
+					s.errLogger.Printf("Error writing tool response: %v", err)
+				}
+			}
+		case <-ctx.Done():
+			return
+		}
+	}
+}
+
 // readNextLine reads a single line from the input reader in a context-aware manner.
 // It uses channels to make the read operation cancellable via context.
 // Returns the read line and any error encountered. If the context is cancelled,
@@ -315,6 +381,9 @@ func (s *StdioServer) Listen(
 	stdin io.Reader,
 	stdout io.Writer,
 ) error {
+	// Initialize the tool call queue
+	s.toolCallQueue = make(chan *toolCallWork, s.queueSize)
+
 	// Set a static client context since stdio only has one client
 	if err := s.server.RegisterSession(ctx, &stdioSessionInstance); err != nil {
 		return fmt.Errorf("register session: %w", err)
@@ -332,9 +401,23 @@ func (s *StdioServer) Listen(
 
 	reader := bufio.NewReader(stdin)
 
+	// Start worker pool for tool calls
+	for i := 0; i < s.workerPoolSize; i++ {
+		s.workerWg.Add(1)
+		go s.toolCallWorker(ctx)
+	}
+
 	// Start notification handler
 	go s.handleNotifications(ctx, stdout)
-	return s.processInputStream(ctx, reader, stdout)
+
+	// Process input stream
+	err := s.processInputStream(ctx, reader, stdout)
+
+	// Shutdown workers gracefully
+	close(s.toolCallQueue)
+	s.workerWg.Wait()
+
+	return err
 }
 
 // processMessage handles a single JSON-RPC message and writes the response.
@@ -367,16 +450,25 @@ func (s *StdioServer) processMessage(
 		Method string `json:"method"`
 	}
 	if json.Unmarshal(rawMessage, &baseMessage) == nil && baseMessage.Method == "tools/call" {
-		// Process tool calls concurrently to avoid blocking on sampling requests
-		go func() {
+		// Queue tool calls for processing by workers
+		select {
+		case s.toolCallQueue <- &toolCallWork{
+			ctx:     ctx,
+			message: rawMessage,
+			writer:  writer,
+		}:
+			return nil
+		case <-ctx.Done():
+			return ctx.Err()
+		default:
+			// Queue is full, process synchronously as fallback
+			s.errLogger.Printf("Tool call queue full, processing synchronously")
 			response := s.server.HandleMessage(ctx, rawMessage)
 			if response != nil {
-				if err := s.writeResponse(response, writer); err != nil {
-					s.errLogger.Printf("Error writing tool response: %v", err)
-				}
+				return s.writeResponse(response, writer)
 			}
-		}()
-		return nil
+			return nil
+		}
 	}
 
 	// Handle other messages synchronously
@@ -462,6 +554,10 @@ func (s *StdioServer) writeResponse(
 		return err
 	}
 
+	// Protect concurrent writes
+	s.writeMu.Lock()
+	defer s.writeMu.Unlock()
+
 	// Write response followed by newline
 	if _, err := fmt.Fprintf(writer, "%s\n", responseBytes); err != nil {
 		return err
diff --git a/server/streamable_http.go b/server/streamable_http.go
index b4d344a..c97d9b7 100644
--- a/server/streamable_http.go
+++ b/server/streamable_http.go
@@ -8,6 +8,7 @@ import (
 	"mime"
 	"net/http"
 	"net/http/httptest"
+	"os"
 	"strings"
 	"sync"
 	"sync/atomic"
@@ -93,6 +94,15 @@ func WithLogger(logger util.Logger) StreamableHTTPOption {
 	}
 }
 
+// WithTLSCert sets the TLS certificate and key files for HTTPS support.
+// Both certFile and keyFile must be provided to enable TLS.
+func WithTLSCert(certFile, keyFile string) StreamableHTTPOption {
+	return func(s *StreamableHTTPServer) {
+		s.tlsCertFile = certFile
+		s.tlsKeyFile = keyFile
+	}
+}
+
 // StreamableHTTPServer implements a Streamable-http based MCP server.
 // It communicates with clients over HTTP protocol, supporting both direct HTTP responses, and SSE streams.
 // https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http
@@ -115,12 +125,12 @@ func WithLogger(logger util.Logger) StreamableHTTPOption {
 // or `hooks.onRegisterSession` will not be triggered for POST messages.
 //
 // The current implementation does not support the following features from the specification:
-//   - Batching of requests/notifications/responses in arrays.
 //   - Stream Resumability
 type StreamableHTTPServer struct {
 	server            *MCPServer
 	sessionTools      *sessionToolsStore
 	sessionRequestIDs sync.Map // sessionId --> last requestID(*atomic.Int64)
+	activeSessions    sync.Map // sessionId --> *streamableHttpSession (for sampling responses)
 
 	httpServer *http.Server
 	mu         sync.RWMutex
@@ -131,6 +141,9 @@ type StreamableHTTPServer struct {
 	listenHeartbeatInterval time.Duration
 	logger                  util.Logger
 	sessionLogLevels        *sessionLogLevelsStore
+
+	tlsCertFile string
+	tlsKeyFile  string
 }
 
 // NewStreamableHTTPServer creates a new streamable-http server instance
@@ -188,6 +201,19 @@ func (s *StreamableHTTPServer) Start(addr string) error {
 	srv := s.httpServer
 	s.mu.Unlock()
 
+	if s.tlsCertFile != "" || s.tlsKeyFile != "" {
+		if s.tlsCertFile == "" || s.tlsKeyFile == "" {
+			return fmt.Errorf("both TLS cert and key must be provided")
+		}
+		if _, err := os.Stat(s.tlsCertFile); err != nil {
+			return fmt.Errorf("failed to find TLS certificate file: %w", err)
+		}
+		if _, err := os.Stat(s.tlsKeyFile); err != nil {
+			return fmt.Errorf("failed to find TLS key file: %w", err)
+		}
+		return srv.ListenAndServeTLS(s.tlsCertFile, s.tlsKeyFile)
+	}
+
 	return srv.ListenAndServe()
 }
 
@@ -207,10 +233,6 @@ func (s *StreamableHTTPServer) Shutdown(ctx context.Context) error {
 
 // --- internal methods ---
 
-const (
-	headerKeySessionID = "Mcp-Session-Id"
-)
-
 func (s *StreamableHTTPServer) handlePost(w http.ResponseWriter, r *http.Request) {
 	// post request carry request/notification message
 
@@ -228,14 +250,32 @@ func (s *StreamableHTTPServer) handlePost(w http.ResponseWriter, r *http.Request
 		s.writeJSONRPCError(w, nil, mcp.PARSE_ERROR, fmt.Sprintf("read request body error: %v", err))
 		return
 	}
-	var baseMessage struct {
-		Method mcp.MCPMethod `json:"method"`
+	// First, try to parse as a response (sampling responses don't have a method field)
+	var jsonMessage struct {
+		ID     json.RawMessage `json:"id"`
+		Result json.RawMessage `json:"result,omitempty"`
+		Error  json.RawMessage `json:"error,omitempty"`
+		Method mcp.MCPMethod   `json:"method,omitempty"`
 	}
-	if err := json.Unmarshal(rawData, &baseMessage); err != nil {
+	if err := json.Unmarshal(rawData, &jsonMessage); err != nil {
 		s.writeJSONRPCError(w, nil, mcp.PARSE_ERROR, "request body is not valid json")
 		return
 	}
-	isInitializeRequest := baseMessage.Method == mcp.MethodInitialize
+
+	// Check if this is a sampling response (has result/error but no method)
+	isSamplingResponse := jsonMessage.Method == "" && jsonMessage.ID != nil &&
+		(jsonMessage.Result != nil || jsonMessage.Error != nil)
+
+	isInitializeRequest := jsonMessage.Method == mcp.MethodInitialize
+
+	// Handle sampling responses separately
+	if isSamplingResponse {
+		if err := s.handleSamplingResponse(w, r, jsonMessage); err != nil {
+			s.logger.Errorf("Failed to handle sampling response: %v", err)
+			http.Error(w, "Failed to handle sampling response", http.StatusInternalServerError)
+		}
+		return
+	}
 
 	// Prepare the session for the mcp server
 	// The session is ephemeral. Its life is the same as the request. It's only created
@@ -247,7 +287,7 @@ func (s *StreamableHTTPServer) handlePost(w http.ResponseWriter, r *http.Request
 	} else {
 		// Get session ID from header.
 		// Stateful servers need the client to carry the session ID.
-		sessionID = r.Header.Get(headerKeySessionID)
+		sessionID = r.Header.Get(HeaderKeySessionID)
 		isTerminated, err := s.sessionIdManager.Validate(sessionID)
 		if err != nil {
 			http.Error(w, "Invalid session ID", http.StatusBadRequest)
@@ -347,7 +387,7 @@ func (s *StreamableHTTPServer) handlePost(w http.ResponseWriter, r *http.Request
 		w.Header().Set("Content-Type", "application/json")
 		if isInitializeRequest && sessionID != "" {
 			// send the session ID back to the client
-			w.Header().Set(headerKeySessionID, sessionID)
+			w.Header().Set(HeaderKeySessionID, sessionID)
 		}
 		w.WriteHeader(http.StatusOK)
 		err := json.NewEncoder(w).Encode(response)
@@ -361,7 +401,7 @@ func (s *StreamableHTTPServer) handleGet(w http.ResponseWriter, r *http.Request)
 	// get request is for listening to notifications
 	// https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#listening-for-messages-from-the-server
 
-	sessionID := r.Header.Get(headerKeySessionID)
+	sessionID := r.Header.Get(HeaderKeySessionID)
 	// the specification didn't say we should validate the session id
 
 	if sessionID == "" {
@@ -377,6 +417,10 @@ func (s *StreamableHTTPServer) handleGet(w http.ResponseWriter, r *http.Request)
 	}
 	defer s.server.UnregisterSession(r.Context(), sessionID)
 
+	// Register session for sampling response delivery
+	s.activeSessions.Store(sessionID, session)
+	defer s.activeSessions.Delete(sessionID)
+
 	// Set the client context before handling the message
 	w.Header().Set("Content-Type", "text/event-stream")
 	w.Header().Set("Cache-Control", "no-cache")
@@ -404,6 +448,21 @@ func (s *StreamableHTTPServer) handleGet(w http.ResponseWriter, r *http.Request)
 				case <-done:
 					return
 				}
+			case samplingReq := <-session.samplingRequestChan:
+				// Send sampling request to client via SSE
+				jsonrpcRequest := mcp.JSONRPCRequest{
+					JSONRPC: "2.0",
+					ID:      mcp.NewRequestId(samplingReq.requestID),
+					Request: mcp.Request{
+						Method: string(mcp.MethodSamplingCreateMessage),
+					},
+					Params: samplingReq.request.CreateMessageParams,
+				}
+				select {
+				case writeChan <- jsonrpcRequest:
+				case <-done:
+					return
+				}
 			case <-done:
 				return
 			}
@@ -460,7 +519,7 @@ func (s *StreamableHTTPServer) handleGet(w http.ResponseWriter, r *http.Request)
 
 func (s *StreamableHTTPServer) handleDelete(w http.ResponseWriter, r *http.Request) {
 	// delete request terminate the session
-	sessionID := r.Header.Get(headerKeySessionID)
+	sessionID := r.Header.Get(HeaderKeySessionID)
 	notAllowed, err := s.sessionIdManager.Terminate(sessionID)
 	if err != nil {
 		http.Error(w, fmt.Sprintf("Session termination failed: %v", err), http.StatusInternalServerError)
@@ -492,6 +551,114 @@ func writeSSEEvent(w io.Writer, data any) error {
 	return nil
 }
 
+// handleSamplingResponse processes incoming sampling responses from clients
+func (s *StreamableHTTPServer) handleSamplingResponse(w http.ResponseWriter, r *http.Request, responseMessage struct {
+	ID     json.RawMessage `json:"id"`
+	Result json.RawMessage `json:"result,omitempty"`
+	Error  json.RawMessage `json:"error,omitempty"`
+	Method mcp.MCPMethod   `json:"method,omitempty"`
+}) error {
+	// Get session ID from header
+	sessionID := r.Header.Get(HeaderKeySessionID)
+	if sessionID == "" {
+		http.Error(w, "Missing session ID for sampling response", http.StatusBadRequest)
+		return fmt.Errorf("missing session ID")
+	}
+
+	// Validate session
+	isTerminated, err := s.sessionIdManager.Validate(sessionID)
+	if err != nil {
+		http.Error(w, "Invalid session ID", http.StatusBadRequest)
+		return err
+	}
+	if isTerminated {
+		http.Error(w, "Session terminated", http.StatusNotFound)
+		return fmt.Errorf("session terminated")
+	}
+
+	// Parse the request ID
+	var requestID int64
+	if err := json.Unmarshal(responseMessage.ID, &requestID); err != nil {
+		http.Error(w, "Invalid request ID in sampling response", http.StatusBadRequest)
+		return err
+	}
+
+	// Create the sampling response item
+	response := samplingResponseItem{
+		requestID: requestID,
+	}
+
+	// Parse result or error
+	if responseMessage.Error != nil {
+		// Parse error
+		var jsonrpcError struct {
+			Code    int    `json:"code"`
+			Message string `json:"message"`
+		}
+		if err := json.Unmarshal(responseMessage.Error, &jsonrpcError); err != nil {
+			response.err = fmt.Errorf("failed to parse error: %v", err)
+		} else {
+			response.err = fmt.Errorf("sampling error %d: %s", jsonrpcError.Code, jsonrpcError.Message)
+		}
+	} else if responseMessage.Result != nil {
+		// Parse result
+		var result mcp.CreateMessageResult
+		if err := json.Unmarshal(responseMessage.Result, &result); err != nil {
+			response.err = fmt.Errorf("failed to parse sampling result: %v", err)
+		} else {
+			response.result = &result
+		}
+	} else {
+		response.err = fmt.Errorf("sampling response has neither result nor error")
+	}
+
+	// Find the corresponding session and deliver the response
+	// The response is delivered to the specific session identified by sessionID
+	if err := s.deliverSamplingResponse(sessionID, response); err != nil {
+		s.logger.Errorf("Failed to deliver sampling response: %v", err)
+		http.Error(w, "Failed to deliver response", http.StatusInternalServerError)
+		return err
+	}
+
+	// Acknowledge receipt
+	w.WriteHeader(http.StatusOK)
+	return nil
+}
+
+// deliverSamplingResponse delivers a sampling response to the appropriate session
+func (s *StreamableHTTPServer) deliverSamplingResponse(sessionID string, response samplingResponseItem) error {
+	// Look up the active session
+	sessionInterface, ok := s.activeSessions.Load(sessionID)
+	if !ok {
+		return fmt.Errorf("no active session found for session %s", sessionID)
+	}
+
+	session, ok := sessionInterface.(*streamableHttpSession)
+	if !ok {
+		return fmt.Errorf("invalid session type for session %s", sessionID)
+	}
+
+	// Look up the dedicated response channel for this specific request
+	responseChannelInterface, exists := session.samplingRequests.Load(response.requestID)
+	if !exists {
+		return fmt.Errorf("no pending request found for session %s, request %d", sessionID, response.requestID)
+	}
+
+	responseChan, ok := responseChannelInterface.(chan samplingResponseItem)
+	if !ok {
+		return fmt.Errorf("invalid response channel type for session %s, request %d", sessionID, response.requestID)
+	}
+
+	// Attempt to deliver the response with timeout to prevent indefinite blocking
+	select {
+	case responseChan <- response:
+		s.logger.Infof("Delivered sampling response for session %s, request %d", sessionID, response.requestID)
+		return nil
+	default:
+		return fmt.Errorf("failed to deliver sampling response for session %s, request %d: channel full or blocked", sessionID, response.requestID)
+	}
+}
+
 // writeJSONRPCError writes a JSON-RPC error response with the given error details.
 func (s *StreamableHTTPServer) writeJSONRPCError(
 	w http.ResponseWriter,
@@ -578,6 +745,19 @@ func (s *sessionToolsStore) delete(sessionID string) {
 	delete(s.tools, sessionID)
 }
 
+// Sampling support types for HTTP transport
+type samplingRequestItem struct {
+	requestID int64
+	request   mcp.CreateMessageRequest
+	response  chan samplingResponseItem
+}
+
+type samplingResponseItem struct {
+	requestID int64
+	result    *mcp.CreateMessageResult
+	err       error
+}
+
 // streamableHttpSession is a session for streamable-http transport
 // When in POST handlers(request/notification), it's ephemeral, and only exists in the life of the request handler.
 // When in GET handlers(listening), it's a real session, and will be registered in the MCP server.
@@ -587,6 +767,11 @@ type streamableHttpSession struct {
 	tools               *sessionToolsStore
 	upgradeToSSE        atomic.Bool
 	logLevels           *sessionLogLevelsStore
+
+	// Sampling support for bidirectional communication
+	samplingRequestChan chan samplingRequestItem // server -> client sampling requests
+	samplingRequests    sync.Map                 // requestID -> pending sampling request context
+	requestIDCounter    atomic.Int64             // for generating unique request IDs
 }
 
 func newStreamableHttpSession(sessionID string, toolStore *sessionToolsStore, levels *sessionLogLevelsStore) *streamableHttpSession {
@@ -595,6 +780,7 @@ func newStreamableHttpSession(sessionID string, toolStore *sessionToolsStore, le
 		notificationChannel: make(chan mcp.JSONRPCNotification, 100),
 		tools:               toolStore,
 		logLevels:           levels,
+		samplingRequestChan: make(chan samplingRequestItem, 10),
 	}
 	return s
 }
@@ -646,6 +832,49 @@ func (s *streamableHttpSession) UpgradeToSSEWhenReceiveNotification() {
 
 var _ SessionWithStreamableHTTPConfig = (*streamableHttpSession)(nil)
 
+// RequestSampling implements SessionWithSampling interface for HTTP transport
+func (s *streamableHttpSession) RequestSampling(ctx context.Context, request mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) {
+	// Generate unique request ID
+	requestID := s.requestIDCounter.Add(1)
+
+	// Create response channel for this specific request
+	responseChan := make(chan samplingResponseItem, 1)
+
+	// Create the sampling request item
+	samplingRequest := samplingRequestItem{
+		requestID: requestID,
+		request:   request,
+		response:  responseChan,
+	}
+
+	// Store the pending request
+	s.samplingRequests.Store(requestID, responseChan)
+	defer s.samplingRequests.Delete(requestID)
+
+	// Send the sampling request via the channel (non-blocking)
+	select {
+	case s.samplingRequestChan <- samplingRequest:
+		// Request queued successfully
+	case <-ctx.Done():
+		return nil, ctx.Err()
+	default:
+		return nil, fmt.Errorf("sampling request queue is full - server overloaded")
+	}
+
+	// Wait for response or context cancellation
+	select {
+	case response := <-responseChan:
+		if response.err != nil {
+			return nil, response.err
+		}
+		return response.result, nil
+	case <-ctx.Done():
+		return nil, ctx.Err()
+	}
+}
+
+var _ SessionWithSampling = (*streamableHttpSession)(nil)
+
 // --- session id manager ---
 
 type SessionIdManager interface {
diff --git a/www/docs/pages/clients/transports.mdx b/www/docs/pages/clients/transports.mdx
index af25fb6..1a2e6dd 100644
--- a/www/docs/pages/clients/transports.mdx
+++ b/www/docs/pages/clients/transports.mdx
@@ -6,12 +6,12 @@ Learn about transport-specific client implementations and how to choose the righ
 
 MCP-Go provides client implementations for all supported transports. Each transport has different characteristics and is optimized for specific scenarios.
 
-| Transport | Best For | Connection | Real-time | Multi-client |
-|-----------|----------|------------|-----------|--------------|
-| **STDIO** | CLI tools, desktop apps | Process pipes | No | No |
-| **StreamableHTTP** | Web services, APIs | HTTP requests | No | Yes |
-| **SSE** | Web apps, real-time | HTTP + EventSource | Yes | Yes |
-| **In-Process** | Testing, embedded | Direct calls | Yes | No |
+| Transport          | Best For                | Connection         | Real-time | Multi-client |
+| ------------------ | ----------------------- | ------------------ | --------- | ------------ |
+| **STDIO**          | CLI tools, desktop apps | Process pipes      | No        | No           |
+| **StreamableHTTP** | Web services, APIs      | HTTP requests      | No        | Yes          |
+| **SSE**            | Web apps, real-time     | HTTP + EventSource | Yes       | Yes          |
+| **In-Process**     | Testing, embedded       | Direct calls       | Yes       | No           |
 
 ## STDIO Client
 
@@ -65,6 +65,42 @@ func createStdioClient() {
 }
 ```
 
+### STDIO Client with Custom Configuration
+
+```go
+func createCustomStdioClient() {
+    // Create custom logger for debugging
+    logger := myCustomLogger{}
+
+    // Create STDIO client with custom options
+    c, err := client.NewStdioMCPClientWithOptions(
+        "go",
+        []string{"GOCACHE=/tmp/gocache"}, // Custom environment
+        []string{"run", "/path/to/server/main.go"},
+        transport.WithCommandLogger(logger),
+        transport.WithCommandFunc(func(ctx context.Context, command string, args []string, env []string) (*exec.Cmd, error) {
+            cmd := exec.CommandContext(ctx, command, args...)
+            cmd.Env = append(os.Environ(), env...)
+            cmd.Dir = "/path/to/working/directory"
+            return cmd, nil
+        }),
+    )
+    if err != nil {
+        log.Fatal(err)
+    }
+    defer c.Close()
+
+    ctx := context.Background()
+
+    // Initialize connection
+    if err := c.Initialize(ctx); err != nil {
+        log.Fatal(err)
+    }
+
+    // Use the client...
+}
+```
+
 ### STDIO Error Handling
 
 ```go
@@ -175,7 +211,7 @@ func (msc *ManagedStdioClient) monitorProcess() {
             return
         case <-msc.restartChan:
             log.Println("Restarting STDIO client...")
-            
+
             if msc.client != nil {
                 msc.client.Close()
             }
@@ -219,11 +255,11 @@ func (msc *ManagedStdioClient) CallTool(ctx context.Context, req mcp.CallToolReq
 func (msc *ManagedStdioClient) Close() error {
     msc.cancel()
     msc.wg.Wait()
-    
+
     if msc.client != nil {
         return msc.client.Close()
     }
-    
+
     return nil
 }
 
@@ -277,8 +313,12 @@ func createStreamableHTTPClient() {
 
 ```go
 func createCustomStreamableHTTPClient() {
+    // Create custom logger for debugging
+    logger := myCustomLogger{}
+
     // Create StreamableHTTP client with options
     c := client.NewStreamableHttpClient("https://api.example.com/mcp",
+        transport.WithLogger(logger),
         transport.WithHTTPTimeout(30*time.Second),
         transport.WithHTTPHeaders(map[string]string{
             "User-Agent": "MyApp/1.0",
@@ -390,12 +430,13 @@ func (pool *StreamableHTTPClientPool) CallTool(ctx context.Context, req mcp.Call
 ```
 
 ### StreamableHTTP With Preconfigured Session
+
 You can also create a StreamableHTTP client with a preconfigured session, which allows you to reuse the same session across multiple requests
 
 ```go
 func createStreamableHTTPClientWithSession() {
     // Create StreamableHTTP client with options
-    sessionID := // fetch existing session ID 
+    sessionID := // fetch existing session ID
     c := client.NewStreamableHttpClient("https://api.example.com/mcp",
         transport.WithSession(sessionID),
     )
@@ -405,7 +446,7 @@ func createStreamableHTTPClientWithSession() {
     // Use client...
     _, err := c.ListTools(ctx)
     // If the session is terminated, you must reinitialize the client
-    if errors.Is(err, transport.ErrSessionTerminated) { 
+    if errors.Is(err, transport.ErrSessionTerminated) {
         c.Initialize(ctx) // Reinitialize if session is terminated
         // The session ID should change after reinitialization
         sessionID = c.GetSessionId() // Update session ID
@@ -458,6 +499,40 @@ func createSSEClient() {
 }
 ```
 
+### SSE Client with Custom Configuration
+
+```go
+func createCustomSSEClient() {
+    // Create custom logger for debugging
+    logger := myCustomLogger{}
+
+    // Create SSE client with custom options
+    c, err := client.NewSSEMCPClient("http://localhost:8080/mcp/sse",
+        transport.WithSSELogger(logger),
+        transport.WithHeaders(map[string]string{
+            "Authorization": "Bearer your-token",
+            "User-Agent":    "MyApp/1.0",
+        }),
+        transport.WithHTTPClient(&http.Client{
+            Timeout: 30 * time.Second,
+        }),
+    )
+    if err != nil {
+        log.Fatal(err)
+    }
+    defer c.Close()
+
+    ctx := context.Background()
+
+    // Initialize
+    if err := c.Initialize(ctx); err != nil {
+        log.Fatal(err)
+    }
+
+    // Use client...
+}
+```
+
 ### SSE Client with Reconnection
 
 ```go
@@ -501,7 +576,7 @@ func (rsc *ResilientSSEClient) connect() error {
     }
 
     client := client.NewSSEClient(rsc.baseURL)
-    
+
     // Set headers
     for key, value := range rsc.headers {
         client.SetHeader(key, value)
@@ -522,11 +597,11 @@ func (rsc *ResilientSSEClient) reconnectLoop() {
             return
         case <-rsc.reconnectCh:
             log.Println("Reconnecting SSE client...")
-            
+
             for attempt := 1; attempt <= 5; attempt++ {
                 if err := rsc.connect(); err != nil {
                     log.Printf("Reconnection attempt %d failed: %v", attempt, err)
-                    
+
                     backoff := time.Duration(attempt) * time.Second
                     select {
                     case <-time.After(backoff):
@@ -578,14 +653,14 @@ func (rsc *ResilientSSEClient) Subscribe(ctx context.Context) (<-chan mcp.Notifi
 
 func (rsc *ResilientSSEClient) Close() error {
     rsc.cancel()
-    
+
     rsc.mutex.Lock()
     defer rsc.mutex.Unlock()
-    
+
     if rsc.client != nil {
         return rsc.client.Close()
     }
-    
+
     return nil
 }
 
@@ -628,7 +703,7 @@ func (seh *SSEEventHandler) Start() error {
     seh.wg.Add(1)
     go func() {
         defer seh.wg.Done()
-        
+
         for {
             select {
             case notification := <-notifications:
@@ -666,7 +741,7 @@ func (seh *SSEEventHandler) OnToolUpdate(handler func(mcp.Notification)) {
 func (seh *SSEEventHandler) addHandler(method string, handler func(mcp.Notification)) {
     seh.mutex.Lock()
     defer seh.mutex.Unlock()
-    
+
     seh.handlers[method] = append(seh.handlers[method], handler)
 }
 
@@ -691,7 +766,7 @@ In-process clients provide direct communication with servers in the same process
 func createInProcessClient() {
     // Create server
     s := server.NewMCPServer("Test Server", "1.0.0")
-    
+
     // Add tools to server
     s.AddTool(
         mcp.NewTool("test_tool",
@@ -829,16 +904,16 @@ func SelectTransport(req TransportRequirements) string {
     switch {
     case !req.NetworkRequired && req.Performance == "high":
         return "inprocess"
-    
+
     case !req.NetworkRequired && !req.MultiClient:
         return "stdio"
-    
+
     case req.RealTime && req.MultiClient:
         return "sse"
-    
+
     case req.NetworkRequired && req.MultiClient:
         return "streamablehttp"
-    
+
     default:
         return "stdio" // Default fallback
     }
@@ -935,12 +1010,12 @@ func (cf *ClientFactory) CreateClient(transport string) (client.Client, error) {
         if !ok {
             return nil, fmt.Errorf("streamablehttp config not set")
         }
-        
+
         options := []transport.StreamableHTTPCOption{}
         if len(config.Headers) > 0 {
             options = append(options, transport.WithHTTPHeaders(config.Headers))
         }
-        
+
         return client.NewStreamableHttpClient(config.BaseURL, options...), nil
 
     case "sse":
@@ -951,12 +1026,12 @@ func (cf *ClientFactory) CreateClient(transport string) (client.Client, error) {
         if !ok {
             return nil, fmt.Errorf("sse config not set")
         }
-        
+
         options := []transport.ClientOption{}
         if len(config.Headers) > 0 {
             options = append(options, transport.WithHeaders(config.Headers))
         }
-        
+
         return client.NewSSEMCPClient(config.BaseURL, options...)
 
     default:
@@ -967,7 +1042,7 @@ func (cf *ClientFactory) CreateClient(transport string) (client.Client, error) {
 // Usage
 func demonstrateClientFactory() {
     factory := NewClientFactory()
-    
+
     // Configure transports
     factory.SetStdioConfig("go", "run", "server.go")
     factory.SetStreamableHTTPConfig("http://localhost:8080/mcp", map[string]string{
@@ -993,3 +1068,19 @@ func demonstrateClientFactory() {
 }
 ```
 
+## Logging Configuration
+
+All client transports support custom logging.
+Each transport provides a logger option that accepts any implementation of the `util.Logger` interface.
+
+```go
+type myCustomLogger struct {}
+
+func (myCustomLogger) Infof(format string, args ...any) {
+    // TODO
+}
+
+func (myCustomLogger) Errorf(format string, args ...any) {
+    // TODO
+}
+```
diff --git a/www/docs/pages/servers/advanced.mdx b/www/docs/pages/servers/advanced.mdx
index 990599b..7562783 100644
--- a/www/docs/pages/servers/advanced.mdx
+++ b/www/docs/pages/servers/advanced.mdx
@@ -130,7 +130,7 @@ func handleCreateUser(ctx context.Context, req mcp.CallToolRequest, input UserCr
         Status:    "created",
     }
 
-    return mcp.NewToolResultJSON(output), nil
+    return mcp.NewToolResultJSON(output)
 }
 ```
 
@@ -962,4 +962,4 @@ For complete sampling documentation, see **[Server Sampling Guide](/servers/adva
 ## Next Steps
 
 - **[Client Development](/clients)** - Learn to build MCP clients
-- **[Server Basics](/servers/basics)** - Review fundamental concepts
\ No newline at end of file
+- **[Server Basics](/servers/basics)** - Review fundamental concepts
diff --git a/www/docs/pages/servers/basics.mdx b/www/docs/pages/servers/basics.mdx
index 7b33f33..b83bbdf 100644
--- a/www/docs/pages/servers/basics.mdx
+++ b/www/docs/pages/servers/basics.mdx
@@ -182,6 +182,7 @@ Configure transport-specific options:
 httpServer := server.NewStreamableHTTPServer(s,
     server.WithEndpointPath("/mcp"),
     server.WithStateless(true),
+    server.WithTLSCert("/path/to/cert.pem", "/path/to/key.pem"),
 )
 
 if err := httpServer.Start(":8080"); err != nil {
diff --git a/www/docs/pages/servers/tools.mdx b/www/docs/pages/servers/tools.mdx
index e329bd1..7bd5bf7 100644
--- a/www/docs/pages/servers/tools.mdx
+++ b/www/docs/pages/servers/tools.mdx
@@ -101,6 +101,310 @@ mcp.WithNumber("price",
 )
 ```
 
+## Struct-Based Schema Definition
+
+MCP-Go supports defining input and output schemas using Go structs with automatic JSON schema generation. This provides a type-safe alternative to manual parameter definition, especially useful for complex tools with structured inputs and outputs.
+
+### Input Schema with Go Structs
+
+Define your input parameters as a Go struct and use `WithInputSchema`:
+
+```go
+// Define input struct with JSON schema tags
+type SearchRequest struct {
+    Query      string   `json:"query" jsonschema_description:"Search query" jsonschema:"required"`
+    Limit      int      `json:"limit,omitempty" jsonschema_description:"Maximum results" jsonschema:"minimum=1,maximum=100,default=10"`
+    Categories []string `json:"categories,omitempty" jsonschema_description:"Filter by categories"`
+    SortBy     string   `json:"sortBy,omitempty" jsonschema_description:"Sort field" jsonschema:"enum=relevance,enum=date,enum=popularity"`
+}
+
+// Create tool with struct-based input schema
+searchTool := mcp.NewTool("search_products",
+    mcp.WithDescription("Search product catalog"),
+    mcp.WithInputSchema[SearchRequest](),
+)
+```
+
+### Output Schema with Go Structs
+
+Define structured output for predictable tool responses:
+
+```go
+// Define output struct
+type SearchResponse struct {
+    Query       string    `json:"query" jsonschema_description:"Original search query"`
+    TotalCount  int       `json:"totalCount" jsonschema_description:"Total matching products"`
+    Products    []Product `json:"products" jsonschema_description:"Search results"`
+    ProcessedAt time.Time `json:"processedAt" jsonschema_description:"When search was performed"`
+}
+
+type Product struct {
+    ID          string  `json:"id" jsonschema_description:"Product ID"`
+    Name        string  `json:"name" jsonschema_description:"Product name"`
+    Price       float64 `json:"price" jsonschema_description:"Price in USD"`
+    InStock     bool    `json:"inStock" jsonschema_description:"Availability"`
+}
+
+// Create tool with both input and output schemas
+searchTool := mcp.NewTool("search_products",
+    mcp.WithDescription("Search product catalog with structured output"),
+    mcp.WithInputSchema[SearchRequest](),
+    mcp.WithOutputSchema[SearchResponse](),
+)
+```
+
+### Structured Tool Handlers
+
+Use `NewStructuredToolHandler` for type-safe handler implementation:
+
+```go
+func main() {
+    s := server.NewMCPServer("Product Search", "1.0.0",
+        server.WithToolCapabilities(false),
+    )
+
+    // Define tool with input and output schemas
+    searchTool := mcp.NewTool("search_products",
+        mcp.WithDescription("Search product catalog"),
+        mcp.WithInputSchema[SearchRequest](),
+        mcp.WithOutputSchema[SearchResponse](),
+    )
+
+    // Add tool with structured handler
+    s.AddTool(searchTool, mcp.NewStructuredToolHandler(searchProductsHandler))
+    
+    server.ServeStdio(s)
+}
+
+// Handler receives typed input and returns typed output
+func searchProductsHandler(ctx context.Context, req mcp.CallToolRequest, args SearchRequest) (SearchResponse, error) {
+    // Input is already validated and bound to SearchRequest struct
+    limit := args.Limit
+    if limit <= 0 {
+        limit = 10
+    }
+
+    // Perform search logic
+    products := searchDatabase(args.Query, args.Categories, limit)
+
+    // Return structured response
+    return SearchResponse{
+        Query:       args.Query,
+        TotalCount:  len(products),
+        Products:    products,
+        ProcessedAt: time.Now(),
+    }, nil
+}
+```
+
+### Array Output Schema
+
+Tools can return arrays of structured data:
+
+```go
+// Define asset struct
+type Asset struct {
+    ID       string  `json:"id" jsonschema_description:"Asset identifier"`
+    Name     string  `json:"name" jsonschema_description:"Asset name"`
+    Value    float64 `json:"value" jsonschema_description:"Current value"`
+    Currency string  `json:"currency" jsonschema_description:"Currency code"`
+}
+
+// Tool that returns array of assets
+assetsTool := mcp.NewTool("list_assets",
+    mcp.WithDescription("List portfolio assets"),
+    mcp.WithInputSchema[struct {
+        Portfolio string `json:"portfolio" jsonschema_description:"Portfolio ID" jsonschema:"required"`
+    }](),
+    mcp.WithOutputSchema[[]Asset](), // Array output schema
+)
+
+func listAssetsHandler(ctx context.Context, req mcp.CallToolRequest, args struct{ Portfolio string }) ([]Asset, error) {
+    // Return array of assets
+    return []Asset{
+        {ID: "btc", Name: "Bitcoin", Value: 45000.50, Currency: "USD"},
+        {ID: "eth", Name: "Ethereum", Value: 3200.75, Currency: "USD"},
+    }, nil
+}
+```
+
+### Schema Tags Reference
+
+MCP-Go uses the `jsonschema` struct tags for schema generation:
+
+```go
+type ExampleStruct struct {
+    // Required field
+    Name string `json:"name" jsonschema:"required"`
+    
+    // Field with description
+    Age int `json:"age" jsonschema_description:"User age in years"`
+    
+    // Field with constraints
+    Score float64 `json:"score" jsonschema:"minimum=0,maximum=100"`
+    
+    // Enum field
+    Status string `json:"status" jsonschema:"enum=active,enum=inactive,enum=pending"`
+    
+    // Optional field with default
+    PageSize int `json:"pageSize,omitempty" jsonschema:"default=20"`
+    
+    // Array with constraints
+    Tags []string `json:"tags" jsonschema:"minItems=1,maxItems=10"`
+}
+```
+
+### Manual Structured Results
+
+For more control over the response, use `NewTypedToolHandler` with manual result creation:
+
+```go
+manualTool := mcp.NewTool("process_data",
+    mcp.WithDescription("Process data with custom result"),
+    mcp.WithInputSchema[ProcessRequest](),
+    mcp.WithOutputSchema[ProcessResponse](),
+)
+
+s.AddTool(manualTool, mcp.NewTypedToolHandler(manualProcessHandler))
+
+func manualProcessHandler(ctx context.Context, req mcp.CallToolRequest, args ProcessRequest) (*mcp.CallToolResult, error) {
+    // Process the data
+    response := ProcessResponse{
+        Status:      "completed",
+        ProcessedAt: time.Now(),
+        ItemCount:   42,
+    }
+    
+    // Create custom fallback text for backward compatibility
+    fallbackText := fmt.Sprintf("Processed %d items successfully", response.ItemCount)
+    
+    // Return structured result with custom text
+    return mcp.NewToolResultStructured(response, fallbackText), nil
+}
+```
+
+### Complete Example: File Operations with Structured I/O
+
+Here's a complete example using the file operations pattern from earlier, enhanced with structured schemas:
+
+```go
+// Define structured input for file operations
+type FileOperationRequest struct {
+    Path     string `json:"path" jsonschema_description:"File path" jsonschema:"required"`
+    Content  string `json:"content,omitempty" jsonschema_description:"File content (for write operations)"`
+    Encoding string `json:"encoding,omitempty" jsonschema_description:"File encoding" jsonschema:"enum=utf-8,enum=ascii,enum=base64,default=utf-8"`
+}
+
+// Define structured output
+type FileOperationResponse struct {
+    Success   bool      `json:"success" jsonschema_description:"Operation success status"`
+    Path      string    `json:"path" jsonschema_description:"File path"`
+    Message   string    `json:"message" jsonschema_description:"Result message"`
+    Content   string    `json:"content,omitempty" jsonschema_description:"File content (for read operations)"`
+    Size      int64     `json:"size,omitempty" jsonschema_description:"File size in bytes"`
+    Modified  time.Time `json:"modified,omitempty" jsonschema_description:"Last modified time"`
+}
+
+func main() {
+    s := server.NewMCPServer("File Manager", "1.0.0",
+        server.WithToolCapabilities(true),
+    )
+
+    // Create file tool with structured I/O
+    createFileTool := mcp.NewTool("create_file",
+        mcp.WithDescription("Create a new file with content"),
+        mcp.WithInputSchema[FileOperationRequest](),
+        mcp.WithOutputSchema[FileOperationResponse](),
+    )
+
+    // Read file tool
+    readFileTool := mcp.NewTool("read_file",
+        mcp.WithDescription("Read file contents"),
+        mcp.WithInputSchema[struct {
+            Path string `json:"path" jsonschema_description:"File path to read" jsonschema:"required"`
+        }](),
+        mcp.WithOutputSchema[FileOperationResponse](),
+    )
+
+    s.AddTool(createFileTool, mcp.NewStructuredToolHandler(handleCreateFile))
+    s.AddTool(readFileTool, mcp.NewStructuredToolHandler(handleReadFile))
+    
+    server.ServeStdio(s)
+}
+
+func handleCreateFile(ctx context.Context, req mcp.CallToolRequest, args FileOperationRequest) (FileOperationResponse, error) {
+    // Validate path for security
+    if strings.Contains(args.Path, "..") {
+        return FileOperationResponse{
+            Success: false,
+            Path:    args.Path,
+            Message: "Invalid path: directory traversal not allowed",
+        }, nil
+    }
+
+    // Handle different encodings
+    var data []byte
+    switch args.Encoding {
+    case "base64":
+        var err error
+        data, err = base64.StdEncoding.DecodeString(args.Content)
+        if err != nil {
+            return FileOperationResponse{
+                Success: false,
+                Path:    args.Path,
+                Message: fmt.Sprintf("Invalid base64 content: %v", err),
+            }, nil
+        }
+    default:
+        data = []byte(args.Content)
+    }
+
+    // Create file
+    if err := os.WriteFile(args.Path, data, 0644); err != nil {
+        return FileOperationResponse{
+            Success: false,
+            Path:    args.Path,
+            Message: fmt.Sprintf("Failed to create file: %v", err),
+        }, nil
+    }
+
+    // Get file info
+    info, _ := os.Stat(args.Path)
+
+    return FileOperationResponse{
+        Success:  true,
+        Path:     args.Path,
+        Message:  "File created successfully",
+        Size:     info.Size(),
+        Modified: info.ModTime(),
+    }, nil
+}
+
+func handleReadFile(ctx context.Context, req mcp.CallToolRequest, args struct{ Path string }) (FileOperationResponse, error) {
+    // Read file
+    data, err := os.ReadFile(args.Path)
+    if err != nil {
+        return FileOperationResponse{
+            Success: false,
+            Path:    args.Path,
+            Message: fmt.Sprintf("Failed to read file: %v", err),
+        }, nil
+    }
+
+    // Get file info
+    info, _ := os.Stat(args.Path)
+
+    return FileOperationResponse{
+        Success:  true,
+        Path:     args.Path,
+        Message:  "File read successfully",
+        Content:  string(data),
+        Size:     info.Size(),
+        Modified: info.ModTime(),
+    }, nil
+}
+```
+
 ## Tool Handlers
 
 Tool handlers process the actual function calls from LLMs. MCP-Go provides convenient helper methods for safe parameter extraction.
diff --git a/www/docs/pages/transports/http.mdx b/www/docs/pages/transports/http.mdx
index 9d7e308..39089ad 100644
--- a/www/docs/pages/transports/http.mdx
+++ b/www/docs/pages/transports/http.mdx
@@ -367,7 +367,7 @@ func main() {
     mux := http.NewServeMux()
     
     // Add MCP endpoints
-    server.AddMCPRoutes(mux, s, "/mcp")
+    mux.Handle("/mcp", server.NewStreamableHTTPServer(s))
     
     // Add custom endpoints
     mux.HandleFunc("/api/status", handleStatus)
@@ -494,7 +494,7 @@ func handleStatelessTool(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
         return nil, err
     }
     
-    return mcp.NewToolResultJSON(result), nil
+    return mcp.NewToolResultJSON(result)
 }
 
 // Use external storage for persistence
@@ -693,4 +693,4 @@ The headers are automatically populated by the transport layer and are available
 
 - **[In-Process Transport](/transports/inprocess)** - Learn about embedded scenarios
 - **[Client Development](/clients)** - Build MCP clients for HTTP transport
-- **[Server Basics](/servers/basics)** - Review fundamental server concepts
\ No newline at end of file
+- **[Server Basics](/servers/basics)** - Review fundamental server concepts



Test patch:
diff --git a/client/protocol_negotiation_test.go b/client/protocol_negotiation_test.go
new file mode 100644
index 0000000..022b7fc
--- /dev/null
+++ b/client/protocol_negotiation_test.go
@@ -0,0 +1,231 @@
+package client
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/mark3labs/mcp-go/client/transport"
+	"github.com/mark3labs/mcp-go/mcp"
+)
+
+// mockProtocolTransport implements transport.Interface for testing protocol negotiation
+type mockProtocolTransport struct {
+	responses           map[string]string
+	notificationHandler func(mcp.JSONRPCNotification)
+	started             bool
+	closed              bool
+}
+
+func (m *mockProtocolTransport) Start(ctx context.Context) error {
+	m.started = true
+	return nil
+}
+
+func (m *mockProtocolTransport) SendRequest(ctx context.Context, request transport.JSONRPCRequest) (*transport.JSONRPCResponse, error) {
+	responseStr, ok := m.responses[request.Method]
+	if !ok {
+		return nil, fmt.Errorf("no mock response for method %s", request.Method)
+	}
+
+	return &transport.JSONRPCResponse{
+		JSONRPC: "2.0",
+		ID:      request.ID,
+		Result:  json.RawMessage(responseStr),
+	}, nil
+}
+
+func (m *mockProtocolTransport) SendNotification(ctx context.Context, notification mcp.JSONRPCNotification) error {
+	return nil
+}
+
+func (m *mockProtocolTransport) SetNotificationHandler(handler func(notification mcp.JSONRPCNotification)) {
+	m.notificationHandler = handler
+}
+
+func (m *mockProtocolTransport) Close() error {
+	m.closed = true
+	return nil
+}
+
+func (m *mockProtocolTransport) GetSessionId() string {
+	return "mock-session"
+}
+
+func TestProtocolVersionNegotiation(t *testing.T) {
+	tests := []struct {
+		name          string
+		serverVersion string
+		expectError   bool
+		errorContains string
+	}{
+		{
+			name:          "supported latest version",
+			serverVersion: mcp.LATEST_PROTOCOL_VERSION,
+			expectError:   false,
+		},
+		{
+			name:          "supported older version 2025-03-26",
+			serverVersion: "2025-03-26",
+			expectError:   false,
+		},
+		{
+			name:          "supported older version 2024-11-05",
+			serverVersion: "2024-11-05",
+			expectError:   false,
+		},
+		{
+			name:          "unsupported version",
+			serverVersion: "2023-01-01",
+			expectError:   true,
+			errorContains: "unsupported protocol version",
+		},
+		{
+			name:          "unsupported future version",
+			serverVersion: "2030-01-01",
+			expectError:   true,
+			errorContains: "unsupported protocol version",
+		},
+		{
+			name:          "empty protocol version",
+			serverVersion: "",
+			expectError:   true,
+			errorContains: "unsupported protocol version",
+		},
+		{
+			name:          "malformed protocol version - invalid format",
+			serverVersion: "not-a-date",
+			expectError:   true,
+			errorContains: "unsupported protocol version",
+		},
+		{
+			name:          "malformed protocol version - partial date",
+			serverVersion: "2025-06",
+			expectError:   true,
+			errorContains: "unsupported protocol version",
+		},
+		{
+			name:          "malformed protocol version - just numbers",
+			serverVersion: "20250618",
+			expectError:   true,
+			errorContains: "unsupported protocol version",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// Create mock transport that returns specific version
+			mockTransport := &mockProtocolTransport{
+				responses: map[string]string{
+					"initialize": fmt.Sprintf(`{
+						"protocolVersion": "%s",
+						"capabilities": {},
+						"serverInfo": {"name": "test", "version": "1.0"}
+					}`, tt.serverVersion),
+				},
+			}
+
+			client := NewClient(mockTransport)
+
+			_, err := client.Initialize(context.Background(), mcp.InitializeRequest{
+				Params: mcp.InitializeParams{
+					ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
+					ClientInfo:      mcp.Implementation{Name: "test-client", Version: "1.0"},
+					Capabilities:    mcp.ClientCapabilities{},
+				},
+			})
+
+			if tt.expectError {
+				if err == nil {
+					t.Errorf("expected error but got none")
+				} else if !strings.Contains(err.Error(), tt.errorContains) {
+					t.Errorf("expected error containing %q, got %q", tt.errorContains, err.Error())
+				}
+				// Verify it's the correct error type
+				if !mcp.IsUnsupportedProtocolVersion(err) {
+					t.Errorf("expected UnsupportedProtocolVersionError, got %T", err)
+				}
+			} else {
+				if err != nil {
+					t.Errorf("unexpected error: %v", err)
+				}
+				// Verify the protocol version was stored
+				if client.protocolVersion != tt.serverVersion {
+					t.Errorf("expected protocol version %q, got %q", tt.serverVersion, client.protocolVersion)
+				}
+			}
+		})
+	}
+}
+
+// mockHTTPTransport implements both transport.Interface and transport.HTTPConnection
+type mockHTTPTransport struct {
+	mockProtocolTransport
+	protocolVersion string
+}
+
+func (m *mockHTTPTransport) SetProtocolVersion(version string) {
+	m.protocolVersion = version
+}
+
+func TestProtocolVersionHeaderSetting(t *testing.T) {
+	// Create mock HTTP transport
+	mockTransport := &mockHTTPTransport{
+		mockProtocolTransport: mockProtocolTransport{
+			responses: map[string]string{
+				"initialize": fmt.Sprintf(`{
+					"protocolVersion": "%s",
+					"capabilities": {},
+					"serverInfo": {"name": "test", "version": "1.0"}
+				}`, mcp.LATEST_PROTOCOL_VERSION),
+			},
+		},
+	}
+
+	client := NewClient(mockTransport)
+
+	_, err := client.Initialize(context.Background(), mcp.InitializeRequest{
+		Params: mcp.InitializeParams{
+			ProtocolVersion: mcp.LATEST_PROTOCOL_VERSION,
+			ClientInfo:      mcp.Implementation{Name: "test-client", Version: "1.0"},
+			Capabilities:    mcp.ClientCapabilities{},
+		},
+	})
+
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+
+	// Verify SetProtocolVersion was called on HTTP transport
+	if mockTransport.protocolVersion != mcp.LATEST_PROTOCOL_VERSION {
+		t.Errorf("expected SetProtocolVersion to be called with %q, got %q",
+			mcp.LATEST_PROTOCOL_VERSION, mockTransport.protocolVersion)
+	}
+}
+
+func TestUnsupportedProtocolVersionError_Is(t *testing.T) {
+	// Test that errors.Is works correctly with UnsupportedProtocolVersionError
+	err1 := mcp.UnsupportedProtocolVersionError{Version: "2023-01-01"}
+	err2 := mcp.UnsupportedProtocolVersionError{Version: "2024-01-01"}
+
+	// Test Is method
+	if !err1.Is(err2) {
+		t.Error("expected UnsupportedProtocolVersionError.Is to return true for same error type")
+	}
+
+	// Test with different error type
+	otherErr := fmt.Errorf("some other error")
+	if err1.Is(otherErr) {
+		t.Error("expected UnsupportedProtocolVersionError.Is to return false for different error type")
+	}
+
+	// Test IsUnsupportedProtocolVersion helper
+	if !mcp.IsUnsupportedProtocolVersion(err1) {
+		t.Error("expected IsUnsupportedProtocolVersion to return true")
+	}
+	if mcp.IsUnsupportedProtocolVersion(otherErr) {
+		t.Error("expected IsUnsupportedProtocolVersion to return false for different error type")
+	}
+}
diff --git a/client/stdio_test.go b/client/stdio_test.go
index f41e481..7eb6dd3 100644
--- a/client/stdio_test.go
+++ b/client/stdio_test.go
@@ -93,7 +93,7 @@ func TestStdioMCPClient(t *testing.T) {
 		defer cancel()
 
 		request := mcp.InitializeRequest{}
-		request.Params.ProtocolVersion = "1.0"
+		request.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
 		request.Params.ClientInfo = mcp.Implementation{
 			Name:    "test-client",
 			Version: "1.0.0",
diff --git a/client/transport/oauth_test.go b/client/transport/oauth_test.go
index 24dec6e..701bedd 100644
--- a/client/transport/oauth_test.go
+++ b/client/transport/oauth_test.go
@@ -300,3 +300,96 @@ func TestOAuthHandler_ProcessAuthorizationResponse_StateValidation(t *testing.T)
 		t.Errorf("Got ErrInvalidState when expected a different error for empty expected state")
 	}
 }
+
+func TestOAuthHandler_SetExpectedState_CrossRequestScenario(t *testing.T) {
+	// Simulate the scenario where different OAuthHandler instances are used
+	// for initialization and callback steps (different HTTP request handlers)
+
+	config := OAuthConfig{
+		ClientID:              "test-client",
+		RedirectURI:           "http://localhost:8085/callback",
+		Scopes:                []string{"mcp.read", "mcp.write"},
+		TokenStore:            NewMemoryTokenStore(),
+		AuthServerMetadataURL: "http://example.com/.well-known/oauth-authorization-server",
+		PKCEEnabled:           true,
+	}
+
+	// Step 1: First handler instance (initialization request)
+	// This simulates the handler that generates the authorization URL
+	handler1 := NewOAuthHandler(config)
+
+	// Mock the server metadata for the first handler
+	handler1.serverMetadata = &AuthServerMetadata{
+		Issuer:                "http://example.com",
+		AuthorizationEndpoint: "http://example.com/authorize",
+		TokenEndpoint:         "http://example.com/token",
+	}
+
+	// Generate state and get authorization URL (this would typically be done in the init handler)
+	testState := "generated-state-value-123"
+	_, err := handler1.GetAuthorizationURL(context.Background(), testState, "test-code-challenge")
+	if err != nil {
+		// We expect this to fail since we're not actually connecting to a server,
+		// but it should still store the expected state
+		if !strings.Contains(err.Error(), "connection") && !strings.Contains(err.Error(), "dial") {
+			t.Errorf("Expected connection error, got: %v", err)
+		}
+	}
+
+	// Verify the state was stored in the first handler
+	if handler1.GetExpectedState() != testState {
+		t.Errorf("Expected state %s to be stored in first handler, got %s", testState, handler1.GetExpectedState())
+	}
+
+	// Step 2: Second handler instance (callback request)
+	// This simulates a completely separate handler instance that would be created
+	// in a different HTTP request handler for processing the OAuth callback
+	handler2 := NewOAuthHandler(config)
+
+	// Mock the server metadata for the second handler
+	handler2.serverMetadata = &AuthServerMetadata{
+		Issuer:                "http://example.com",
+		AuthorizationEndpoint: "http://example.com/authorize",
+		TokenEndpoint:         "http://example.com/token",
+	}
+
+	// Initially, the second handler has no expected state
+	if handler2.GetExpectedState() != "" {
+		t.Errorf("Expected second handler to have empty state initially, got %s", handler2.GetExpectedState())
+	}
+
+	// Step 3: Transfer the state from the first handler to the second
+	// This is the key functionality being tested - setting the expected state
+	// in a different handler instance
+	handler2.SetExpectedState(testState)
+
+	// Verify the state was transferred correctly
+	if handler2.GetExpectedState() != testState {
+		t.Errorf("Expected state %s to be set in second handler, got %s", testState, handler2.GetExpectedState())
+	}
+
+	// Step 4: Test that state validation works correctly in the second handler
+
+	// Test with correct state - should pass validation but fail at token exchange
+	// (since we're not actually running a real OAuth server)
+	err = handler2.ProcessAuthorizationResponse(context.Background(), "test-code", testState, "test-code-verifier")
+	if err == nil {
+		t.Errorf("Expected error due to token exchange failure, got nil")
+	}
+	// Should NOT be ErrInvalidState since the state matches
+	if errors.Is(err, ErrInvalidState) {
+		t.Errorf("Got ErrInvalidState with matching state, should have failed at token exchange instead")
+	}
+
+	// Verify state was cleared after processing (even though token exchange failed)
+	if handler2.GetExpectedState() != "" {
+		t.Errorf("Expected state to be cleared after processing, got %s", handler2.GetExpectedState())
+	}
+
+	// Step 5: Test with wrong state after resetting
+	handler2.SetExpectedState("different-state-value")
+	err = handler2.ProcessAuthorizationResponse(context.Background(), "test-code", testState, "test-code-verifier")
+	if !errors.Is(err, ErrInvalidState) {
+		t.Errorf("Expected ErrInvalidState with wrong state, got %v", err)
+	}
+}
diff --git a/client/transport/sse_test.go b/client/transport/sse_test.go
index a672e02..31c7088 100644
--- a/client/transport/sse_test.go
+++ b/client/transport/sse_test.go
@@ -4,17 +4,52 @@ import (
 	"context"
 	"encoding/json"
 	"errors"
-	"sync"
-	"testing"
-	"time"
-
 	"fmt"
+	"io"
 	"net/http"
 	"net/http/httptest"
+	"strings"
+	"sync"
+	"testing"
+	"time"
 
 	"github.com/mark3labs/mcp-go/mcp"
+	"github.com/stretchr/testify/require"
 )
 
+// mockReaderWithError is a mock io.ReadCloser that simulates reading some data
+// and then returning a specific error
+type mockReaderWithError struct {
+	data     []byte
+	err      error
+	position int
+	closed   bool
+}
+
+func (m *mockReaderWithError) Read(p []byte) (n int, err error) {
+	if m.closed {
+		return 0, io.EOF
+	}
+
+	if m.position >= len(m.data) {
+		return 0, m.err
+	}
+
+	n = copy(p, m.data[m.position:])
+	m.position += n
+
+	if m.position >= len(m.data) {
+		return n, m.err
+	}
+
+	return n, nil
+}
+
+func (m *mockReaderWithError) Close() error {
+	m.closed = true
+	return nil
+}
+
 // startMockSSEEchoServer starts a test HTTP server that implements
 // a minimal SSE-based echo server for testing purposes.
 // It returns the server URL and a function to close the server.
@@ -115,7 +150,6 @@ func startMockSSEEchoServer() (string, func()) {
 				flush()
 			}
 		}()
-
 	})
 
 	// Create a router to handle different endpoints
@@ -228,7 +262,6 @@ func TestSSE(t *testing.T) {
 	})
 
 	t.Run("SendNotification & NotificationHandler", func(t *testing.T) {
-
 		var wg sync.WaitGroup
 		notificationChan := make(chan mcp.JSONRPCNotification, 1)
 
@@ -368,7 +401,6 @@ func TestSSE(t *testing.T) {
 	})
 
 	t.Run("ResponseError", func(t *testing.T) {
-
 		// Prepare a request
 		request := JSONRPCRequest{
 			JSONRPC: "2.0",
@@ -408,9 +440,9 @@ func TestSSE(t *testing.T) {
 	t.Run("SSEEventWithoutEventField", func(t *testing.T) {
 		// Test that SSE events with only data field (no event field) are processed correctly
 		// This tests the fix for issue #369
-		
+
 		var messageReceived chan struct{}
-		
+
 		// Create a custom mock server that sends SSE events without event field
 		sseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			w.Header().Set("Content-Type", "text/event-stream")
@@ -449,7 +481,7 @@ func TestSSE(t *testing.T) {
 		messageHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			w.Header().Set("Content-Type", "application/json")
 			w.WriteHeader(http.StatusAccepted)
-			
+
 			// Signal that message was received
 			close(messageReceived)
 		})
@@ -508,6 +540,217 @@ func TestSSE(t *testing.T) {
 		}
 	})
 
+	t.Run("NO_ERROR_WithoutConnectionLostHandler", func(t *testing.T) {
+		// Test that NO_ERROR without connection lost handler maintains backward compatibility
+		// When no connection lost handler is set, NO_ERROR should be treated as a regular error
+
+		// Create a mock Reader that simulates NO_ERROR
+		mockReader := &mockReaderWithError{
+			data: []byte("event: endpoint\ndata: /message\n\n"),
+			err:  errors.New("connection closed: NO_ERROR"),
+		}
+
+		// Create SSE transport
+		url, closeF := startMockSSEEchoServer()
+		defer closeF()
+
+		trans, err := NewSSE(url)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		// DO NOT set connection lost handler to test backward compatibility
+
+		// Capture stderr to verify the error is printed (backward compatible behavior)
+		// Since we can't easily capture fmt.Printf output in tests, we'll just verify
+		// that the readSSE method returns without calling any handler
+
+		// Directly test the readSSE method with our mock reader
+		go trans.readSSE(mockReader)
+
+		// Wait for readSSE to complete
+		time.Sleep(100 * time.Millisecond)
+
+		// The test passes if readSSE completes without panicking or hanging
+		// In backward compatibility mode, NO_ERROR should be treated as a regular error
+		t.Log("Backward compatibility test passed: NO_ERROR handled as regular error when no handler is set")
+	})
+
+	t.Run("NO_ERROR_ConnectionLost", func(t *testing.T) {
+		// Test that NO_ERROR in HTTP/2 connection loss is properly handled
+		// This test verifies that when a connection is lost in a way that produces
+		// an error message containing "NO_ERROR", the connection lost handler is called
+
+		var connectionLostCalled bool
+		var connectionLostError error
+		var mu sync.Mutex
+
+		// Create a mock Reader that simulates connection loss with NO_ERROR
+		mockReader := &mockReaderWithError{
+			data: []byte("event: endpoint\ndata: /message\n\n"),
+			err:  errors.New("http2: stream closed with error code NO_ERROR"),
+		}
+
+		// Create SSE transport
+		url, closeF := startMockSSEEchoServer()
+		defer closeF()
+
+		trans, err := NewSSE(url)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		// Set connection lost handler
+		trans.SetConnectionLostHandler(func(err error) {
+			mu.Lock()
+			defer mu.Unlock()
+			connectionLostCalled = true
+			connectionLostError = err
+		})
+
+		// Directly test the readSSE method with our mock reader that simulates NO_ERROR
+		go trans.readSSE(mockReader)
+
+		// Wait for connection lost handler to be called
+		timeout := time.After(1 * time.Second)
+		ticker := time.NewTicker(10 * time.Millisecond)
+		defer ticker.Stop()
+
+		for {
+			select {
+			case <-timeout:
+				t.Fatal("Connection lost handler was not called within timeout for NO_ERROR connection loss")
+			case <-ticker.C:
+				mu.Lock()
+				called := connectionLostCalled
+				err := connectionLostError
+				mu.Unlock()
+
+				if called {
+					if err == nil {
+						t.Fatal("Expected connection lost error, got nil")
+					}
+
+					// Verify that the error contains "NO_ERROR" string
+					if !strings.Contains(err.Error(), "NO_ERROR") {
+						t.Errorf("Expected error to contain 'NO_ERROR', got: %v", err)
+					}
+
+					t.Logf("Connection lost handler called with NO_ERROR: %v", err)
+					return
+				}
+			}
+		}
+	})
+
+	t.Run("NO_ERROR_Handling", func(t *testing.T) {
+		// Test specific NO_ERROR string handling in readSSE method
+		// This tests the code path at line 209 where NO_ERROR is checked
+
+		// Create a mock Reader that simulates an error containing "NO_ERROR"
+		mockReader := &mockReaderWithError{
+			data: []byte("event: endpoint\ndata: /message\n\n"),
+			err:  errors.New("connection closed: NO_ERROR"),
+		}
+
+		// Create SSE transport
+		url, closeF := startMockSSEEchoServer()
+		defer closeF()
+
+		trans, err := NewSSE(url)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		var connectionLostCalled bool
+		var connectionLostError error
+		var mu sync.Mutex
+
+		// Set connection lost handler to verify it's called for NO_ERROR
+		trans.SetConnectionLostHandler(func(err error) {
+			mu.Lock()
+			defer mu.Unlock()
+			connectionLostCalled = true
+			connectionLostError = err
+		})
+
+		// Directly test the readSSE method with our mock reader
+		go trans.readSSE(mockReader)
+
+		// Wait for connection lost handler to be called
+		timeout := time.After(1 * time.Second)
+		ticker := time.NewTicker(10 * time.Millisecond)
+		defer ticker.Stop()
+
+		for {
+			select {
+			case <-timeout:
+				t.Fatal("Connection lost handler was not called within timeout for NO_ERROR")
+			case <-ticker.C:
+				mu.Lock()
+				called := connectionLostCalled
+				err := connectionLostError
+				mu.Unlock()
+
+				if called {
+					if err == nil {
+						t.Fatal("Expected connection lost error with NO_ERROR, got nil")
+					}
+
+					// Verify that the error contains "NO_ERROR" string
+					if !strings.Contains(err.Error(), "NO_ERROR") {
+						t.Errorf("Expected error to contain 'NO_ERROR', got: %v", err)
+					}
+
+					t.Logf("Successfully handled NO_ERROR: %v", err)
+					return
+				}
+			}
+		}
+	})
+
+	t.Run("RegularError_DoesNotTriggerConnectionLost", func(t *testing.T) {
+		// Test that regular errors (not containing NO_ERROR) do not trigger connection lost handler
+
+		// Create a mock Reader that simulates a regular error
+		mockReader := &mockReaderWithError{
+			data: []byte("event: endpoint\ndata: /message\n\n"),
+			err:  errors.New("regular connection error"),
+		}
+
+		// Create SSE transport
+		url, closeF := startMockSSEEchoServer()
+		defer closeF()
+
+		trans, err := NewSSE(url)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		var connectionLostCalled bool
+		var mu sync.Mutex
+
+		// Set connection lost handler - this should NOT be called for regular errors
+		trans.SetConnectionLostHandler(func(err error) {
+			mu.Lock()
+			defer mu.Unlock()
+			connectionLostCalled = true
+		})
+
+		// Directly test the readSSE method with our mock reader
+		go trans.readSSE(mockReader)
+
+		// Wait and verify connection lost handler is NOT called
+		time.Sleep(200 * time.Millisecond)
+
+		mu.Lock()
+		called := connectionLostCalled
+		mu.Unlock()
+
+		if called {
+			t.Error("Connection lost handler should not be called for regular errors")
+		}
+	})
 }
 
 func TestSSEErrors(t *testing.T) {
@@ -624,4 +867,49 @@ func TestSSEErrors(t *testing.T) {
 		}
 	})
 
+	t.Run("SSEStreamErrorLogging", func(t *testing.T) {
+		logChan := make(chan string, 10)
+		testLogger := &testLogger{logChan: logChan}
+
+		sseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			w.Header().Set("Content-Type", "text/event-stream")
+			flusher, ok := w.(http.Flusher)
+			if !ok {
+				http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
+				return
+			}
+
+			fmt.Fprintf(w, "event: endpoint\ndata: %s\n\n", "/message")
+			flusher.Flush()
+
+			fmt.Fprintf(w, "event: message\ndata: {invalid json}\n\n")
+			flusher.Flush()
+
+			time.Sleep(50 * time.Millisecond)
+		})
+
+		testServer := httptest.NewServer(sseHandler)
+		t.Cleanup(testServer.Close)
+
+		trans, err := NewSSE(testServer.URL, WithSSELogger(testLogger))
+		require.NoError(t, err)
+
+		// Start the transport
+		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
+		t.Cleanup(cancel)
+
+		err = trans.Start(ctx)
+		require.NoError(t, err)
+		t.Cleanup(func() { _ = trans.Close() })
+
+		// Wait for the error log message about unmarshaling
+		select {
+		case logMsg := <-logChan:
+			if !strings.Contains(logMsg, "Error unmarshaling message") {
+				t.Errorf("Expected error log about unmarshaling message, got: %s", logMsg)
+			}
+		case <-time.After(3 * time.Second):
+			t.Fatal("Timeout waiting for error log message")
+		}
+	})
 }
diff --git a/client/transport/stdio_test.go b/client/transport/stdio_test.go
index 3c6804f..18aa932 100644
--- a/client/transport/stdio_test.go
+++ b/client/transport/stdio_test.go
@@ -5,18 +5,19 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"io"
 	"os"
 	"os/exec"
 	"path/filepath"
 	"runtime"
+	"strings"
 	"sync"
 	"syscall"
 	"testing"
 	"time"
 
-	"github.com/stretchr/testify/require"
-
 	"github.com/mark3labs/mcp-go/mcp"
+	"github.com/stretchr/testify/require"
 )
 
 func compileTestServer(outputPath string) error {
@@ -508,6 +509,70 @@ func TestStdioErrors(t *testing.T) {
 			t.Errorf("Expected error when sending request after close, got nil")
 		}
 	})
+
+	t.Run("StdioResponseWritingErrorLogging", func(t *testing.T) {
+		logChan := make(chan string, 10)
+		testLogger := &testLogger{logChan: logChan}
+
+		_, stdinWriter := io.Pipe()
+		stdoutReader, stdoutWriter := io.Pipe()
+		stderrReader, stderrWriter := io.Pipe()
+		t.Cleanup(func() {
+			_ = stdinWriter.Close()
+			_ = stdoutWriter.Close()
+			_ = stderrWriter.Close()
+		})
+
+		stdio := NewIO(stdoutReader, stdinWriter, stderrReader)
+		stdio.logger = testLogger
+
+		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
+		t.Cleanup(cancel)
+
+		err := stdio.Start(ctx)
+		if err != nil {
+			t.Fatalf("Failed to start stdio transport: %v", err)
+		}
+		t.Cleanup(func() { _ = stdio.Close() })
+
+		stdio.SetRequestHandler(func(ctx context.Context, request JSONRPCRequest) (*JSONRPCResponse, error) {
+			return &JSONRPCResponse{
+				JSONRPC: "2.0",
+				ID:      request.ID,
+				Result:  json.RawMessage(`"test response"`),
+			}, nil
+		})
+
+		doneChan := make(chan struct{})
+		go func() {
+			// Simulate a request coming from the server
+			request := JSONRPCRequest{
+				JSONRPC: "2.0",
+				ID:      mcp.NewRequestId(int64(1)),
+				Method:  "test/method",
+			}
+			requestBytes, _ := json.Marshal(request)
+			requestBytes = append(requestBytes, '\n')
+			_, _ = stdoutWriter.Write(requestBytes)
+
+			// Close stdin to trigger a write error when the response is sent
+			time.Sleep(50 * time.Millisecond) // Give time for the request to be processed
+			_ = stdinWriter.Close()
+			doneChan <- struct{}{}
+		}()
+
+		<-doneChan
+
+		// Wait for the error log message
+		select {
+		case logMsg := <-logChan:
+			if !strings.Contains(logMsg, "Error writing response") {
+				t.Errorf("Expected error log about writing response, got: %s", logMsg)
+			}
+		case <-time.After(3 * time.Second):
+			t.Fatal("Timeout waiting for error log message")
+		}
+	})
 }
 
 func TestStdio_WithCommandFunc(t *testing.T) {
diff --git a/client/transport/streamable_http_sampling_test.go b/client/transport/streamable_http_sampling_test.go
new file mode 100644
index 0000000..4a38f28
--- /dev/null
+++ b/client/transport/streamable_http_sampling_test.go
@@ -0,0 +1,496 @@
+package transport
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"sync"
+	"testing"
+	"time"
+
+	"github.com/mark3labs/mcp-go/mcp"
+)
+
+// TestStreamableHTTP_SamplingFlow tests the complete sampling flow with HTTP transport
+func TestStreamableHTTP_SamplingFlow(t *testing.T) {
+	// Create simple test server
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		// Just respond OK to any requests
+		w.WriteHeader(http.StatusOK)
+	}))
+	defer server.Close()
+
+	// Create HTTP client transport
+	client, err := NewStreamableHTTP(server.URL)
+	if err != nil {
+		t.Fatalf("Failed to create client: %v", err)
+	}
+	defer client.Close()
+
+	// Set up sampling request handler
+	var handledRequest *JSONRPCRequest
+	handlerCalled := make(chan struct{})
+	client.SetRequestHandler(func(ctx context.Context, request JSONRPCRequest) (*JSONRPCResponse, error) {
+		handledRequest = &request
+		close(handlerCalled)
+
+		// Simulate sampling handler response
+		result := map[string]any{
+			"role": "assistant",
+			"content": map[string]any{
+				"type": "text",
+				"text": "Hello! How can I help you today?",
+			},
+			"model":      "test-model",
+			"stopReason": "stop_sequence",
+		}
+
+		resultBytes, _ := json.Marshal(result)
+
+		return &JSONRPCResponse{
+			JSONRPC: "2.0",
+			ID:      request.ID,
+			Result:  resultBytes,
+		}, nil
+	})
+
+	// Start the client
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+
+	err = client.Start(ctx)
+	if err != nil {
+		t.Fatalf("Failed to start client: %v", err)
+	}
+
+	// Test direct request handling (simulating a sampling request)
+	samplingRequest := JSONRPCRequest{
+		JSONRPC: "2.0",
+		ID:      mcp.NewRequestId(1),
+		Method:  string(mcp.MethodSamplingCreateMessage),
+		Params: map[string]any{
+			"messages": []map[string]any{
+				{
+					"role": "user",
+					"content": map[string]any{
+						"type": "text",
+						"text": "Hello, world!",
+					},
+				},
+			},
+		},
+	}
+
+	// Directly test request handling
+	client.handleIncomingRequest(ctx, samplingRequest)
+
+	// Wait for handler to be called
+	select {
+	case <-handlerCalled:
+		// Handler was called
+	case <-time.After(1 * time.Second):
+		t.Fatal("Handler was not called within timeout")
+	}
+
+	// Verify the request was handled
+	if handledRequest == nil {
+		t.Fatal("Sampling request was not handled")
+	}
+
+	if handledRequest.Method != string(mcp.MethodSamplingCreateMessage) {
+		t.Errorf("Expected method %s, got %s", mcp.MethodSamplingCreateMessage, handledRequest.Method)
+	}
+}
+
+// TestStreamableHTTP_SamplingErrorHandling tests error handling in sampling requests
+func TestStreamableHTTP_SamplingErrorHandling(t *testing.T) {
+	var errorHandled sync.WaitGroup
+	errorHandled.Add(1)
+
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if r.Method == http.MethodPost {
+			var body map[string]any
+			if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
+				t.Logf("Failed to decode body: %v", err)
+				w.WriteHeader(http.StatusOK)
+				return
+			}
+
+			// Check if this is an error response
+			if errorField, ok := body["error"]; ok {
+				errorMap := errorField.(map[string]any)
+				if code, ok := errorMap["code"].(float64); ok && code == -32603 {
+					errorHandled.Done()
+					w.WriteHeader(http.StatusOK)
+					return
+				}
+			}
+		}
+		w.WriteHeader(http.StatusOK)
+	}))
+	defer server.Close()
+
+	client, err := NewStreamableHTTP(server.URL)
+	if err != nil {
+		t.Fatalf("Failed to create client: %v", err)
+	}
+	defer client.Close()
+
+	// Set up request handler that returns an error
+	client.SetRequestHandler(func(ctx context.Context, request JSONRPCRequest) (*JSONRPCResponse, error) {
+		return nil, fmt.Errorf("sampling failed")
+	})
+
+	// Start the client
+	ctx := context.Background()
+	err = client.Start(ctx)
+	if err != nil {
+		t.Fatalf("Failed to start client: %v", err)
+	}
+
+	// Simulate incoming sampling request
+	samplingRequest := JSONRPCRequest{
+		JSONRPC: "2.0",
+		ID:      mcp.NewRequestId(1),
+		Method:  string(mcp.MethodSamplingCreateMessage),
+		Params:  map[string]any{},
+	}
+
+	// This should trigger error handling
+	client.handleIncomingRequest(ctx, samplingRequest)
+
+	// Wait for error to be handled
+	errorHandled.Wait()
+}
+
+// TestStreamableHTTP_NoSamplingHandler tests behavior when no sampling handler is set
+func TestStreamableHTTP_NoSamplingHandler(t *testing.T) {
+	var errorReceived bool
+	errorReceivedChan := make(chan struct{})
+
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if r.Method == http.MethodPost {
+			var body map[string]any
+			if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
+				t.Logf("Failed to decode body: %v", err)
+				w.WriteHeader(http.StatusOK)
+				return
+			}
+
+			// Check if this is an error response with method not found
+			if errorField, ok := body["error"]; ok {
+				errorMap := errorField.(map[string]any)
+				if code, ok := errorMap["code"].(float64); ok && code == -32601 {
+					if message, ok := errorMap["message"].(string); ok &&
+						strings.Contains(message, "no handler configured") {
+						errorReceived = true
+						close(errorReceivedChan)
+					}
+				}
+			}
+		}
+		w.WriteHeader(http.StatusOK)
+	}))
+	defer server.Close()
+
+	client, err := NewStreamableHTTP(server.URL)
+	if err != nil {
+		t.Fatalf("Failed to create client: %v", err)
+	}
+	defer client.Close()
+
+	// Don't set any request handler
+
+	ctx := context.Background()
+	err = client.Start(ctx)
+	if err != nil {
+		t.Fatalf("Failed to start client: %v", err)
+	}
+
+	// Simulate incoming sampling request
+	samplingRequest := JSONRPCRequest{
+		JSONRPC: "2.0",
+		ID:      mcp.NewRequestId(1),
+		Method:  string(mcp.MethodSamplingCreateMessage),
+		Params:  map[string]any{},
+	}
+
+	// This should trigger "method not found" error
+	client.handleIncomingRequest(ctx, samplingRequest)
+
+	// Wait for error to be received
+	select {
+	case <-errorReceivedChan:
+		// Error was received
+	case <-time.After(1 * time.Second):
+		t.Fatal("Method not found error was not received within timeout")
+	}
+
+	if !errorReceived {
+		t.Error("Expected method not found error, but didn't receive it")
+	}
+}
+
+// TestStreamableHTTP_BidirectionalInterface verifies the interface implementation
+func TestStreamableHTTP_BidirectionalInterface(t *testing.T) {
+	client, err := NewStreamableHTTP("http://example.com")
+	if err != nil {
+		t.Fatalf("Failed to create client: %v", err)
+	}
+	defer client.Close()
+
+	// Verify it implements BidirectionalInterface
+	_, ok := any(client).(BidirectionalInterface)
+	if !ok {
+		t.Error("StreamableHTTP should implement BidirectionalInterface")
+	}
+
+	// Test SetRequestHandler
+	handlerSet := false
+	handlerSetChan := make(chan struct{})
+	client.SetRequestHandler(func(ctx context.Context, request JSONRPCRequest) (*JSONRPCResponse, error) {
+		handlerSet = true
+		close(handlerSetChan)
+		return nil, nil
+	})
+
+	// Verify handler was set by triggering it
+	ctx := context.Background()
+	client.handleIncomingRequest(ctx, JSONRPCRequest{
+		JSONRPC: "2.0",
+		ID:      mcp.NewRequestId(1),
+		Method:  "test",
+	})
+
+	// Wait for handler to be called
+	select {
+	case <-handlerSetChan:
+		// Handler was called
+	case <-time.After(1 * time.Second):
+		t.Fatal("Handler was not called within timeout")
+	}
+
+	if !handlerSet {
+		t.Error("Request handler was not properly set or called")
+	}
+}
+
+// TestStreamableHTTP_ConcurrentSamplingRequests tests concurrent sampling requests
+// where the second request completes faster than the first request
+func TestStreamableHTTP_ConcurrentSamplingRequests(t *testing.T) {
+	var receivedResponses []map[string]any
+	var responseMutex sync.Mutex
+	responseComplete := make(chan struct{}, 2)
+
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if r.Method == http.MethodPost {
+			var body map[string]any
+			if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
+				t.Logf("Failed to decode body: %v", err)
+				w.WriteHeader(http.StatusBadRequest)
+				return
+			}
+
+			// Check if this is a response from client (not a request)
+			if _, ok := body["result"]; ok {
+				responseMutex.Lock()
+				receivedResponses = append(receivedResponses, body)
+				responseMutex.Unlock()
+				responseComplete <- struct{}{}
+			}
+		}
+		w.WriteHeader(http.StatusOK)
+	}))
+	defer server.Close()
+
+	client, err := NewStreamableHTTP(server.URL)
+	if err != nil {
+		t.Fatalf("Failed to create client: %v", err)
+	}
+	defer client.Close()
+
+	// Track which requests have been received and their completion order
+	var requestOrder []int
+	var orderMutex sync.Mutex
+
+	// Set up request handler that simulates different processing times
+	client.SetRequestHandler(func(ctx context.Context, request JSONRPCRequest) (*JSONRPCResponse, error) {
+		// Extract request ID to determine processing time
+		requestIDValue := request.ID.Value()
+
+		var delay time.Duration
+		var responseText string
+		var requestNum int
+
+		// First request (ID 1) takes longer, second request (ID 2) completes faster
+		if requestIDValue == int64(1) {
+			delay = 100 * time.Millisecond
+			responseText = "Response from slow request 1"
+			requestNum = 1
+		} else if requestIDValue == int64(2) {
+			delay = 10 * time.Millisecond
+			responseText = "Response from fast request 2"
+			requestNum = 2
+		} else {
+			t.Errorf("Unexpected request ID: %v", requestIDValue)
+			return nil, fmt.Errorf("unexpected request ID")
+		}
+
+		// Simulate processing time
+		time.Sleep(delay)
+
+		// Record completion order
+		orderMutex.Lock()
+		requestOrder = append(requestOrder, requestNum)
+		orderMutex.Unlock()
+
+		// Return response with correct request ID
+		result := map[string]any{
+			"role": "assistant",
+			"content": map[string]any{
+				"type": "text",
+				"text": responseText,
+			},
+			"model":      "test-model",
+			"stopReason": "stop_sequence",
+		}
+
+		resultBytes, _ := json.Marshal(result)
+
+		return &JSONRPCResponse{
+			JSONRPC: "2.0",
+			ID:      request.ID,
+			Result:  resultBytes,
+		}, nil
+	})
+
+	// Start the client
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+
+	err = client.Start(ctx)
+	if err != nil {
+		t.Fatalf("Failed to start client: %v", err)
+	}
+
+	// Create two sampling requests with different IDs
+	request1 := JSONRPCRequest{
+		JSONRPC: "2.0",
+		ID:      mcp.NewRequestId(int64(1)),
+		Method:  string(mcp.MethodSamplingCreateMessage),
+		Params: map[string]any{
+			"messages": []map[string]any{
+				{
+					"role": "user",
+					"content": map[string]any{
+						"type": "text",
+						"text": "Slow request 1",
+					},
+				},
+			},
+		},
+	}
+
+	request2 := JSONRPCRequest{
+		JSONRPC: "2.0",
+		ID:      mcp.NewRequestId(int64(2)),
+		Method:  string(mcp.MethodSamplingCreateMessage),
+		Params: map[string]any{
+			"messages": []map[string]any{
+				{
+					"role": "user",
+					"content": map[string]any{
+						"type": "text",
+						"text": "Fast request 2",
+					},
+				},
+			},
+		},
+	}
+
+	// Send both requests concurrently
+	go client.handleIncomingRequest(ctx, request1)
+	go client.handleIncomingRequest(ctx, request2)
+
+	// Wait for both responses to complete
+	for i := 0; i < 2; i++ {
+		select {
+		case <-responseComplete:
+			// Response received
+		case <-time.After(2 * time.Second):
+			t.Fatal("Timeout waiting for response")
+		}
+	}
+
+	// Verify completion order: request 2 should complete first
+	orderMutex.Lock()
+	defer orderMutex.Unlock()
+
+	if len(requestOrder) != 2 {
+		t.Fatalf("Expected 2 completed requests, got %d", len(requestOrder))
+	}
+
+	if requestOrder[0] != 2 {
+		t.Errorf("Expected request 2 to complete first, but request %d completed first", requestOrder[0])
+	}
+
+	if requestOrder[1] != 1 {
+		t.Errorf("Expected request 1 to complete second, but request %d completed second", requestOrder[1])
+	}
+
+	// Verify responses are correctly associated
+	responseMutex.Lock()
+	defer responseMutex.Unlock()
+
+	if len(receivedResponses) != 2 {
+		t.Fatalf("Expected 2 responses, got %d", len(receivedResponses))
+	}
+
+	// Find responses by ID
+	var response1, response2 map[string]any
+	for _, resp := range receivedResponses {
+		if id, ok := resp["id"]; ok {
+			switch id {
+			case int64(1), float64(1):
+				response1 = resp
+			case int64(2), float64(2):
+				response2 = resp
+			}
+		}
+	}
+
+	if response1 == nil {
+		t.Error("Response for request 1 not found")
+	}
+	if response2 == nil {
+		t.Error("Response for request 2 not found")
+	}
+
+	// Verify each response contains the correct content
+	if response1 != nil {
+		if result, ok := response1["result"].(map[string]any); ok {
+			if content, ok := result["content"].(map[string]any); ok {
+				if text, ok := content["text"].(string); ok {
+					if !strings.Contains(text, "slow request 1") {
+						t.Errorf("Response 1 should contain 'slow request 1', got: %s", text)
+					}
+				}
+			}
+		}
+	}
+
+	if response2 != nil {
+		if result, ok := response2["result"].(map[string]any); ok {
+			if content, ok := result["content"].(map[string]any); ok {
+				if text, ok := content["text"].(string); ok {
+					if !strings.Contains(text, "fast request 2") {
+						t.Errorf("Response 2 should contain 'fast request 2', got: %s", text)
+					}
+				}
+			}
+		}
+	}
+}
diff --git a/client/transport/streamable_http_test.go b/client/transport/streamable_http_test.go
index 2596294..5208cb9 100644
--- a/client/transport/streamable_http_test.go
+++ b/client/transport/streamable_http_test.go
@@ -418,7 +418,7 @@ func TestStreamableHTTP(t *testing.T) {
 	t.Run("SSEEventWithoutEventField", func(t *testing.T) {
 		// Test that SSE events with only data field (no event field) are processed correctly
 		// This tests the fix for issue #369
-		
+
 		// Create a custom mock server that sends SSE events without event field
 		handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			if r.Method != http.MethodPost {
@@ -438,7 +438,7 @@ func TestStreamableHTTP(t *testing.T) {
 			// This should be processed as a "message" event according to SSE spec
 			w.Header().Set("Content-Type", "text/event-stream")
 			w.WriteHeader(http.StatusOK)
-			
+
 			response := map[string]any{
 				"jsonrpc": "2.0",
 				"id":      request["id"],
@@ -523,7 +523,6 @@ func TestStreamableHTTPErrors(t *testing.T) {
 			t.Errorf("Expected error when sending request to non-existent URL, got nil")
 		}
 	})
-
 }
 
 // ---- continuous listening tests ----
@@ -718,7 +717,6 @@ func TestContinuousListening(t *testing.T) {
 }
 
 func TestContinuousListeningMethodNotAllowed(t *testing.T) {
-
 	// Start a server that doesn't support GET
 	url, closeServer, _, _ := startMockStreamableWithGETSupport(false)
 
diff --git a/mcp/tools_test.go b/mcp/tools_test.go
index 0cd7123..4bb07aa 100644
--- a/mcp/tools_test.go
+++ b/mcp/tools_test.go
@@ -306,7 +306,6 @@ func TestParseToolCallToolRequest(t *testing.T) {
 	param15 := ParseInt64(request, "string_value", 1)
 	assert.Equal(t, fmt.Sprintf("%T", param15), "int64")
 	t.Logf("param15 type: %T,value:%v", param15, param15)
-
 }
 
 func TestCallToolRequestBindArguments(t *testing.T) {
@@ -529,6 +528,691 @@ func TestFlexibleArgumentsJSONMarshalUnmarshal(t *testing.T) {
 	assert.Equal(t, float64(123), args["key2"]) // JSON numbers are unmarshaled as float64
 }
 
+// TestToolWithInputSchema tests that the WithInputSchema function
+// generates an MCP-compatible JSON output schema for a tool
+func TestToolWithInputSchema(t *testing.T) {
+	type TestInput struct {
+		Name  string `json:"name" jsonschema_description:"Person's name" jsonschema:"required"`
+		Age   int    `json:"age" jsonschema_description:"Person's age"`
+		Email string `json:"email,omitempty" jsonschema_description:"Email address" jsonschema:"required"`
+	}
+
+	tool := NewTool("test_tool",
+		WithDescription("Test tool with output schema"),
+		WithInputSchema[TestInput](),
+	)
+
+	// Check that RawOutputSchema was set
+	assert.NotNil(t, tool.RawInputSchema)
+
+	// Marshal and verify structure
+	data, err := json.Marshal(tool)
+	assert.NoError(t, err)
+
+	var toolData map[string]any
+	err = json.Unmarshal(data, &toolData)
+	assert.NoError(t, err)
+
+	// Verify inputSchema exists
+	inputSchema, exists := toolData["inputSchema"]
+	assert.True(t, exists)
+	assert.NotNil(t, inputSchema)
+
+	// Verify required list exists
+	schemaMap, ok := inputSchema.(map[string]interface{})
+	assert.True(t, ok)
+	requiredList, exists := schemaMap["required"]
+	assert.True(t, exists)
+	assert.NotNil(t, requiredList)
+
+	// Verify properties exist
+	properties, exists := schemaMap["properties"]
+	assert.True(t, exists)
+	propertiesMap, ok := properties.(map[string]interface{})
+	assert.True(t, ok)
+
+	// Verify specific properties
+	assert.Contains(t, propertiesMap, "name")
+	assert.Contains(t, propertiesMap, "age")
+	assert.Contains(t, propertiesMap, "email")
+}
+
+// TestToolWithOutputSchema tests that the WithOutputSchema function
+// generates an MCP-compatible JSON output schema for a tool
+func TestToolWithOutputSchema(t *testing.T) {
+	type TestOutput struct {
+		Name  string `json:"name" jsonschema_description:"Person's name"`
+		Age   int    `json:"age" jsonschema_description:"Person's age"`
+		Email string `json:"email,omitempty" jsonschema_description:"Email address"`
+	}
+
+	tests := []struct {
+		name                 string
+		tool                 Tool
+		expectedOutputSchema bool
+	}{
+		{
+			name: "default behavior",
+			tool: NewTool("test_tool",
+				WithDescription("Test tool with output schema"),
+				WithOutputSchema[TestOutput](),
+				WithString("input", Required()),
+			),
+			expectedOutputSchema: true,
+		},
+		{
+			name: "no output schema is set",
+			tool: NewTool("test_tool",
+				WithDescription("Test tool with no output schema"),
+				WithString("input", Required()),
+			),
+			expectedOutputSchema: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// Marshal and verify structure
+			data, err := json.Marshal(tt.tool)
+			assert.NoError(t, err)
+
+			var toolData map[string]any
+			err = json.Unmarshal(data, &toolData)
+			assert.NoError(t, err)
+
+			// Verify outputSchema exists
+			outputSchema, exists := toolData["outputSchema"]
+			if tt.expectedOutputSchema {
+				assert.True(t, exists)
+				assert.NotNil(t, outputSchema)
+			} else {
+				assert.False(t, exists)
+				assert.Nil(t, outputSchema)
+			}
+		})
+	}
+}
+
+// TestNewToolResultStructured tests that the NewToolResultStructured function
+// creates a CallToolResult with both structured and text content
+func TestNewToolResultStructured(t *testing.T) {
+	testData := map[string]any{
+		"message": "Success",
+		"count":   42,
+		"active":  true,
+	}
+
+	result := NewToolResultStructured(testData, "Fallback text")
+
+	assert.Len(t, result.Content, 1)
+
+	textContent, ok := result.Content[0].(TextContent)
+	assert.True(t, ok)
+	assert.Equal(t, "Fallback text", textContent.Text)
+	assert.NotNil(t, result.StructuredContent)
+}
+
+// TestCallToolResultMarshalJSON tests the custom JSON marshaling of CallToolResult
+func TestCallToolResultMarshalJSON(t *testing.T) {
+	tests := []struct {
+		name     string
+		result   CallToolResult
+		expected map[string]any
+	}{
+		{
+			name: "basic result with text content",
+			result: CallToolResult{
+				Result: Result{
+					Meta: NewMetaFromMap(map[string]any{"key": "value"}),
+				},
+				Content: []Content{
+					TextContent{Type: "text", Text: "Hello, world!"},
+				},
+				IsError: false,
+			},
+			expected: map[string]any{
+				"_meta": map[string]any{"key": "value"},
+				"content": []any{
+					map[string]any{
+						"type": "text",
+						"text": "Hello, world!",
+					},
+				},
+			},
+		},
+		{
+			name: "result with structured content",
+			result: CallToolResult{
+				Result: Result{
+					Meta: NewMetaFromMap(map[string]any{"key": "value"}),
+				},
+				Content: []Content{
+					TextContent{Type: "text", Text: "Operation completed"},
+				},
+				StructuredContent: map[string]any{
+					"status":  "success",
+					"count":   42,
+					"message": "Data processed successfully",
+				},
+				IsError: false,
+			},
+			expected: map[string]any{
+				"_meta": map[string]any{"key": "value"},
+				"content": []any{
+					map[string]any{
+						"type": "text",
+						"text": "Operation completed",
+					},
+				},
+				"structuredContent": map[string]any{
+					"status":  "success",
+					"count":   float64(42), // JSON numbers are unmarshaled as float64
+					"message": "Data processed successfully",
+				},
+			},
+		},
+		{
+			name: "error result",
+			result: CallToolResult{
+				Result: Result{
+					Meta: NewMetaFromMap(map[string]any{"error_code": "E001"}),
+				},
+				Content: []Content{
+					TextContent{Type: "text", Text: "An error occurred"},
+				},
+				IsError: true,
+			},
+			expected: map[string]any{
+				"_meta": map[string]any{"error_code": "E001"},
+				"content": []any{
+					map[string]any{
+						"type": "text",
+						"text": "An error occurred",
+					},
+				},
+				"isError": true,
+			},
+		},
+		{
+			name: "result with multiple content types",
+			result: CallToolResult{
+				Result: Result{
+					Meta: NewMetaFromMap(map[string]any{"session_id": "12345"}),
+				},
+				Content: []Content{
+					TextContent{Type: "text", Text: "Processing complete"},
+					ImageContent{Type: "image", Data: "base64-encoded-image-data", MIMEType: "image/jpeg"},
+				},
+				StructuredContent: map[string]any{
+					"processed_items": 100,
+					"errors":          0,
+				},
+				IsError: false,
+			},
+			expected: map[string]any{
+				"_meta": map[string]any{"session_id": "12345"},
+				"content": []any{
+					map[string]any{
+						"type": "text",
+						"text": "Processing complete",
+					},
+					map[string]any{
+						"type":     "image",
+						"data":     "base64-encoded-image-data",
+						"mimeType": "image/jpeg",
+					},
+				},
+				"structuredContent": map[string]any{
+					"processed_items": float64(100),
+					"errors":          float64(0),
+				},
+			},
+		},
+		{
+			name: "result with nil structured content",
+			result: CallToolResult{
+				Result: Result{
+					Meta: NewMetaFromMap(map[string]any{"key": "value"}),
+				},
+				Content: []Content{
+					TextContent{Type: "text", Text: "Simple result"},
+				},
+				StructuredContent: nil,
+				IsError:           false,
+			},
+			expected: map[string]any{
+				"_meta": map[string]any{"key": "value"},
+				"content": []any{
+					map[string]any{
+						"type": "text",
+						"text": "Simple result",
+					},
+				},
+			},
+		},
+		{
+			name: "result with empty content array",
+			result: CallToolResult{
+				Result: Result{
+					Meta: NewMetaFromMap(map[string]any{"key": "value"}),
+				},
+				Content: []Content{},
+				StructuredContent: map[string]any{
+					"data": "structured only",
+				},
+				IsError: false,
+			},
+			expected: map[string]any{
+				"_meta":   map[string]any{"key": "value"},
+				"content": []any{},
+				"structuredContent": map[string]any{
+					"data": "structured only",
+				},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// Marshal the result
+			data, err := json.Marshal(tt.result)
+			assert.NoError(t, err)
+
+			// Unmarshal to map for comparison
+			var result map[string]any
+			err = json.Unmarshal(data, &result)
+			assert.NoError(t, err)
+
+			// Compare expected fields
+			for key, expectedValue := range tt.expected {
+				assert.Contains(t, result, key, "Result should contain key: %s", key)
+				assert.Equal(t, expectedValue, result[key], "Value for key %s should match", key)
+			}
+
+			// Verify that unexpected fields are not present
+			for key := range result {
+				if key != "_meta" && key != "content" && key != "structuredContent" && key != "isError" {
+					t.Errorf("Unexpected field in result: %s", key)
+				}
+			}
+		})
+	}
+}
+
+// TestCallToolResultUnmarshalJSON tests the custom JSON unmarshaling of CallToolResult
+func TestCallToolResultUnmarshalJSON(t *testing.T) {
+	tests := []struct {
+		name     string
+		jsonData string
+		expected CallToolResult
+		wantErr  bool
+	}{
+		{
+			name: "basic result with text content",
+			jsonData: `{
+				"_meta": {"key": "value"},
+				"content": [
+					{"type": "text", "text": "Hello, world!"}
+				]
+			}`,
+			expected: CallToolResult{
+				Result: Result{
+					Meta: NewMetaFromMap(map[string]any{"key": "value"}),
+				},
+				Content: []Content{
+					TextContent{Type: "text", Text: "Hello, world!"},
+				},
+				IsError: false,
+			},
+			wantErr: false,
+		},
+		{
+			name: "result with structured content",
+			jsonData: `{
+				"_meta": {"key": "value"},
+				"content": [
+					{"type": "text", "text": "Operation completed"}
+				],
+				"structuredContent": {
+					"status": "success",
+					"count": 42,
+					"message": "Data processed successfully"
+				}
+			}`,
+			expected: CallToolResult{
+				Result: Result{
+					Meta: NewMetaFromMap(map[string]any{"key": "value"}),
+				},
+				Content: []Content{
+					TextContent{Type: "text", Text: "Operation completed"},
+				},
+				StructuredContent: map[string]any{
+					"status":  "success",
+					"count":   float64(42),
+					"message": "Data processed successfully",
+				},
+				IsError: false,
+			},
+			wantErr: false,
+		},
+		{
+			name: "error result",
+			jsonData: `{
+				"_meta": {"error_code": "E001"},
+				"content": [
+					{"type": "text", "text": "An error occurred"}
+				],
+				"isError": true
+			}`,
+			expected: CallToolResult{
+				Result: Result{
+					Meta: NewMetaFromMap(map[string]any{"error_code": "E001"}),
+				},
+				Content: []Content{
+					TextContent{Type: "text", Text: "An error occurred"},
+				},
+				IsError: true,
+			},
+			wantErr: false,
+		},
+		{
+			name: "result with multiple content types",
+			jsonData: `{
+				"_meta": {"session_id": "12345"},
+				"content": [
+					{"type": "text", "text": "Processing complete"},
+					{"type": "image", "data": "base64-encoded-image-data", "mimeType": "image/jpeg"}
+				],
+				"structuredContent": {
+					"processed_items": 100,
+					"errors": 0
+				}
+			}`,
+			expected: CallToolResult{
+				Result: Result{
+					Meta: NewMetaFromMap(map[string]any{"session_id": "12345"}),
+				},
+				Content: []Content{
+					TextContent{Type: "text", Text: "Processing complete"},
+					ImageContent{Type: "image", Data: "base64-encoded-image-data", MIMEType: "image/jpeg"},
+				},
+				StructuredContent: map[string]any{
+					"processed_items": float64(100),
+					"errors":          float64(0),
+				},
+				IsError: false,
+			},
+			wantErr: false,
+		},
+		{
+			name: "result with nil structured content",
+			jsonData: `{
+				"_meta": {"key": "value"},
+				"content": [
+					{"type": "text", "text": "Simple result"}
+				]
+			}`,
+			expected: CallToolResult{
+				Result: Result{
+					Meta: NewMetaFromMap(map[string]any{"key": "value"}),
+				},
+				Content: []Content{
+					TextContent{Type: "text", Text: "Simple result"},
+				},
+				StructuredContent: nil,
+				IsError:           false,
+			},
+			wantErr: false,
+		},
+		{
+			name: "result with empty content array",
+			jsonData: `{
+				"_meta": {"key": "value"},
+				"content": [],
+				"structuredContent": {
+					"data": "structured only"
+				}
+			}`,
+			expected: CallToolResult{
+				Result: Result{
+					Meta: NewMetaFromMap(map[string]any{"key": "value"}),
+				},
+				Content: []Content{},
+				StructuredContent: map[string]any{
+					"data": "structured only",
+				},
+				IsError: false,
+			},
+			wantErr: false,
+		},
+		{
+			name:     "invalid JSON",
+			jsonData: `{invalid json}`,
+			wantErr:  true,
+		},
+		{
+			name: "result with missing content field",
+			jsonData: `{
+				"_meta": {"key": "value"},
+				"structuredContent": {"data": "no content"}
+			}`,
+			expected: CallToolResult{
+				Result: Result{
+					Meta: NewMetaFromMap(map[string]any{"key": "value"}),
+				},
+				Content: nil,
+				StructuredContent: map[string]any{
+					"data": "no content",
+				},
+				IsError: false,
+			},
+			wantErr: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var result CallToolResult
+			err := json.Unmarshal([]byte(tt.jsonData), &result)
+
+			if tt.wantErr {
+				assert.Error(t, err)
+				return
+			}
+
+			assert.NoError(t, err)
+
+			// Compare Meta
+			if tt.expected.Meta != nil {
+				assert.Equal(t, tt.expected.Meta, result.Meta)
+			}
+
+			// Compare Content
+			assert.Len(t, result.Content, len(tt.expected.Content))
+			for i, expectedContent := range tt.expected.Content {
+				if i < len(result.Content) {
+					// Compare content types and values
+					switch expected := expectedContent.(type) {
+					case TextContent:
+						if actual, ok := result.Content[i].(TextContent); ok {
+							assert.Equal(t, expected.Text, actual.Text)
+						} else {
+							t.Errorf("Expected TextContent at index %d, got %T", i, result.Content[i])
+						}
+					case ImageContent:
+						if actual, ok := result.Content[i].(ImageContent); ok {
+							assert.Equal(t, expected.Data, actual.Data)
+							assert.Equal(t, expected.MIMEType, actual.MIMEType)
+						} else {
+							t.Errorf("Expected ImageContent at index %d, got %T", i, result.Content[i])
+						}
+					}
+				}
+			}
+
+			// Compare StructuredContent
+			assert.Equal(t, tt.expected.StructuredContent, result.StructuredContent)
+
+			// Compare IsError
+			assert.Equal(t, tt.expected.IsError, result.IsError)
+		})
+	}
+}
+
+// TestCallToolResultRoundTrip tests that marshaling and unmarshaling preserves all data
+func TestCallToolResultRoundTrip(t *testing.T) {
+	original := CallToolResult{
+		Result: Result{
+			Meta: NewMetaFromMap(map[string]any{
+				"session_id": "12345",
+				"user_id":    "user123",
+				"timestamp":  "2024-01-01T00:00:00Z",
+			}),
+		},
+		Content: []Content{
+			TextContent{Type: "text", Text: "Operation started"},
+			ImageContent{Type: "image", Data: "base64-encoded-chart-data", MIMEType: "image/png"},
+			TextContent{Type: "text", Text: "Operation completed successfully"},
+		},
+		StructuredContent: map[string]any{
+			"status":          "success",
+			"processed_count": float64(150.0),
+			"error_count":     float64(0.0),
+			"warnings":        []any{"Minor issue detected"},
+			"metadata": map[string]any{
+				"version": "1.0.0",
+				"build":   "2024-01-01",
+			},
+		},
+		IsError: false,
+	}
+
+	// Marshal to JSON
+	data, err := json.Marshal(original)
+	assert.NoError(t, err)
+
+	// Unmarshal back
+	var unmarshaled CallToolResult
+	err = json.Unmarshal(data, &unmarshaled)
+	assert.NoError(t, err)
+
+	// Verify all fields are preserved
+	assert.Equal(t, original.Meta, unmarshaled.Meta)
+	assert.Equal(t, original.IsError, unmarshaled.IsError)
+	assert.Equal(t, original.StructuredContent, unmarshaled.StructuredContent)
+
+	// Verify content array
+	assert.Len(t, unmarshaled.Content, len(original.Content))
+	for i, expectedContent := range original.Content {
+		if i < len(unmarshaled.Content) {
+			switch expected := expectedContent.(type) {
+			case TextContent:
+				if actual, ok := unmarshaled.Content[i].(TextContent); ok {
+					assert.Equal(t, expected.Text, actual.Text)
+				} else {
+					t.Errorf("Expected TextContent at index %d, got %T", i, unmarshaled.Content[i])
+				}
+			case ImageContent:
+				if actual, ok := unmarshaled.Content[i].(ImageContent); ok {
+					assert.Equal(t, expected.Data, actual.Data)
+					assert.Equal(t, expected.MIMEType, actual.MIMEType)
+				} else {
+					t.Errorf("Expected ImageContent at index %d, got %T", i, unmarshaled.Content[i])
+				}
+			}
+		}
+	}
+}
+
+// TestCallToolResultEdgeCases tests edge cases for CallToolResult marshaling/unmarshaling
+func TestCallToolResultEdgeCases(t *testing.T) {
+	tests := []struct {
+		name     string
+		result   CallToolResult
+		jsonData string
+	}{
+		{
+			name: "result with complex structured content",
+			result: CallToolResult{
+				Content: []Content{
+					TextContent{Type: "text", Text: "Complex data returned"},
+				},
+				StructuredContent: map[string]any{
+					"nested": map[string]any{
+						"array": []any{1, 2, 3, "string", true, nil},
+						"object": map[string]any{
+							"deep": map[string]any{
+								"value": "very deep",
+							},
+						},
+					},
+					"mixed_types": []any{
+						map[string]any{"type": "object"},
+						"string",
+						42.5,
+						true,
+						nil,
+					},
+				},
+			},
+		},
+		{
+			name: "result with empty structured content object",
+			result: CallToolResult{
+				Content: []Content{
+					TextContent{Type: "text", Text: "Empty structured content"},
+				},
+				StructuredContent: map[string]any{},
+			},
+		},
+		{
+			name: "result with null structured content in JSON",
+			jsonData: `{
+				"content": [{"type": "text", "text": "Null structured content"}],
+				"structuredContent": null
+			}`,
+		},
+		{
+			name: "result with missing isError field",
+			jsonData: `{
+				"content": [{"type": "text", "text": "No error field"}]
+			}`,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var data []byte
+			var err error
+
+			if tt.jsonData != "" {
+				// Test unmarshaling from JSON
+				var result CallToolResult
+				err = json.Unmarshal([]byte(tt.jsonData), &result)
+				assert.NoError(t, err)
+
+				// Verify the result can be marshaled back
+				data, err = json.Marshal(result)
+				assert.NoError(t, err)
+			} else {
+				// Test marshaling the result
+				data, err = json.Marshal(tt.result)
+				assert.NoError(t, err)
+
+				// Verify it can be unmarshaled back
+				var result CallToolResult
+				err = json.Unmarshal(data, &result)
+				assert.NoError(t, err)
+			}
+
+			// Verify the JSON is valid
+			var jsonMap map[string]any
+			err = json.Unmarshal(data, &jsonMap)
+			assert.NoError(t, err)
+		})
+	}
+}
+
 // TestNewItemsAPICompatibility tests that the new Items API functions
 // generate the same schema as the original Items() function with manual schema objects
 func TestNewItemsAPICompatibility(t *testing.T) {
diff --git a/mcptest/mcptest_test.go b/mcptest/mcptest_test.go
index 18922cb..3e4be38 100644
--- a/mcptest/mcptest_test.go
+++ b/mcptest/mcptest_test.go
@@ -78,6 +78,95 @@ func resultToString(result *mcp.CallToolResult) (string, error) {
 	return b.String(), nil
 }
 
+func TestServerWithToolStructuredContent(t *testing.T) {
+	ctx := context.Background()
+
+	srv, err := mcptest.NewServer(t, server.ServerTool{
+		Tool: mcp.NewTool("get_user",
+			mcp.WithDescription("Gets user information with structured data."),
+			mcp.WithString("user_id", mcp.Description("The user ID to look up.")),
+		),
+		Handler: structuredContentHandler,
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer srv.Close()
+
+	client := srv.Client()
+
+	var req mcp.CallToolRequest
+	req.Params.Name = "get_user"
+	req.Params.Arguments = map[string]any{
+		"user_id": "123",
+	}
+
+	result, err := client.CallTool(ctx, req)
+	if err != nil {
+		t.Fatal("CallTool:", err)
+	}
+
+	if result.IsError {
+		t.Fatalf("unexpected error result: %+v", result)
+	}
+
+	if len(result.Content) != 1 {
+		t.Fatalf("Expected 1 content item, got %d", len(result.Content))
+	}
+
+	// Check text content (fallback)
+	textContent, ok := result.Content[0].(mcp.TextContent)
+	if !ok {
+		t.Fatalf("Expected content to be TextContent, got %T", result.Content[0])
+	}
+	expectedText := "User found"
+	if textContent.Text != expectedText {
+		t.Errorf("Expected text %q, got %q", expectedText, textContent.Text)
+	}
+
+	// Check structured content
+	if result.StructuredContent == nil {
+		t.Fatal("Expected StructuredContent to be present")
+	}
+
+	structuredData, ok := result.StructuredContent.(map[string]any)
+	if !ok {
+		t.Fatalf("Expected StructuredContent to be map[string]any, got %T", result.StructuredContent)
+	}
+
+	// Verify structured data
+	if structuredData["id"] != "123" {
+		t.Errorf("Expected id '123', got %v", structuredData["id"])
+	}
+	if structuredData["name"] != "John Doe" {
+		t.Errorf("Expected name 'John Doe', got %v", structuredData["name"])
+	}
+	if structuredData["email"] != "john@example.com" {
+		t.Errorf("Expected email 'john@example.com', got %v", structuredData["email"])
+	}
+	if structuredData["active"] != true {
+		t.Errorf("Expected active true, got %v", structuredData["active"])
+	}
+}
+
+func structuredContentHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+	userID, ok := request.GetArguments()["user_id"].(string)
+	if !ok {
+		return mcp.NewToolResultError("user_id parameter is required"), nil
+	}
+
+	// Create structured data
+	userData := map[string]any{
+		"id":     userID,
+		"name":   "John Doe",
+		"email":  "john@example.com",
+		"active": true,
+	}
+
+	// Use NewToolResultStructured to create result with both text fallback and structured content
+	return mcp.NewToolResultStructured(userData, "User found"), nil
+}
+
 func TestServerWithPrompt(t *testing.T) {
 	ctx := context.Background()
 
diff --git a/server/sampling_test.go b/server/sampling_test.go
index c69ac6c..012bf2f 100644
--- a/server/sampling_test.go
+++ b/server/sampling_test.go
@@ -113,3 +113,42 @@ func TestMCPServer_RequestSampling_Success(t *testing.T) {
 		t.Errorf("expected model %q, got %q", "test-model", result.Model)
 	}
 }
+
+func TestMCPServer_EnableSampling_SetsCapability(t *testing.T) {
+	server := NewMCPServer("test", "1.0.0")
+
+	// Verify sampling capability is not set initially
+	ctx := context.Background()
+	initRequest := mcp.InitializeRequest{
+		Params: mcp.InitializeParams{
+			ProtocolVersion: "2025-03-26",
+			ClientInfo: mcp.Implementation{
+				Name:    "test-client",
+				Version: "1.0.0",
+			},
+			Capabilities: mcp.ClientCapabilities{},
+		},
+	}
+
+	result, err := server.handleInitialize(ctx, 1, initRequest)
+	if err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+
+	if result.Capabilities.Sampling != nil {
+		t.Error("sampling capability should not be set before EnableSampling() is called")
+	}
+
+	// Enable sampling
+	server.EnableSampling()
+
+	// Verify sampling capability is now set
+	result, err = server.handleInitialize(ctx, 2, initRequest)
+	if err != nil {
+		t.Fatalf("unexpected error after EnableSampling(): %v", err)
+	}
+
+	if result.Capabilities.Sampling == nil {
+		t.Error("sampling capability should be set after EnableSampling() is called")
+	}
+}
diff --git a/server/server_test.go b/server/server_test.go
index bd7dd47..755082e 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -9,6 +9,7 @@ import (
 	"reflect"
 	"slices"
 	"sort"
+	"sync"
 	"testing"
 	"time"
 
@@ -42,7 +43,7 @@ func TestMCPServer_Capabilities(t *testing.T) {
 
 				assert.Equal(
 					t,
-					mcp.LATEST_PROTOCOL_VERSION,
+					"2025-03-26", // Backward compatibility: no protocol version provided,
 					initResult.ProtocolVersion,
 				)
 				assert.Equal(t, "test-server", initResult.ServerInfo.Name)
@@ -70,7 +71,7 @@ func TestMCPServer_Capabilities(t *testing.T) {
 
 				assert.Equal(
 					t,
-					mcp.LATEST_PROTOCOL_VERSION,
+					"2025-03-26", // Backward compatibility: no protocol version provided,
 					initResult.ProtocolVersion,
 				)
 				assert.Equal(t, "test-server", initResult.ServerInfo.Name)
@@ -107,7 +108,7 @@ func TestMCPServer_Capabilities(t *testing.T) {
 
 				assert.Equal(
 					t,
-					mcp.LATEST_PROTOCOL_VERSION,
+					"2025-03-26", // Backward compatibility: no protocol version provided,
 					initResult.ProtocolVersion,
 				)
 				assert.Equal(t, "test-server", initResult.ServerInfo.Name)
@@ -407,7 +408,7 @@ func TestMCPServer_HandleValidMessages(t *testing.T) {
 
 				assert.Equal(
 					t,
-					mcp.LATEST_PROTOCOL_VERSION,
+					"2025-03-26", // Backward compatibility: no protocol version provided,
 					initResult.ProtocolVersion,
 				)
 				assert.Equal(t, "test-server", initResult.ServerInfo.Name)
@@ -963,6 +964,50 @@ func TestMCPServer_Prompts(t *testing.T) {
 				assert.Equal(t, "test-prompt-2", prompts[1].Name)
 			},
 		},
+		{
+			name: "SetPrompts sends single notifications/prompts/list_changed with one active session",
+			action: func(t *testing.T, server *MCPServer, notificationChannel chan mcp.JSONRPCNotification) {
+				err := server.RegisterSession(context.TODO(), &fakeSession{
+					sessionID:           "test",
+					notificationChannel: notificationChannel,
+					initialized:         true,
+				})
+				require.NoError(t, err)
+				server.SetPrompts(ServerPrompt{
+					Prompt: mcp.Prompt{
+						Name:        "test-prompt-1",
+						Description: "A test prompt",
+						Arguments: []mcp.PromptArgument{
+							{
+								Name:        "arg1",
+								Description: "First argument",
+							},
+						},
+					},
+					Handler: nil,
+				}, ServerPrompt{
+					Prompt: mcp.Prompt{
+						Name:        "test-prompt-2",
+						Description: "Another test prompt",
+						Arguments: []mcp.PromptArgument{
+							{
+								Name:        "arg2",
+								Description: "Second argument",
+							},
+						},
+					},
+					Handler: nil,
+				})
+			},
+			expectedNotifications: 1,
+			validate: func(t *testing.T, notifications []mcp.JSONRPCNotification, promptsList mcp.JSONRPCMessage) {
+				assert.Equal(t, mcp.MethodNotificationPromptsListChanged, notifications[0].Method)
+				prompts := promptsList.(mcp.JSONRPCResponse).Result.(mcp.ListPromptsResult).Prompts
+				assert.Len(t, prompts, 2)
+				assert.Equal(t, "test-prompt-1", prompts[0].Name)
+				assert.Equal(t, "test-prompt-2", prompts[1].Name)
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -998,6 +1043,211 @@ func TestMCPServer_Prompts(t *testing.T) {
 	}
 }
 
+func TestMCPServer_Resources(t *testing.T) {
+	tests := []struct {
+		name                  string
+		action                func(*testing.T, *MCPServer, chan mcp.JSONRPCNotification)
+		expectedNotifications int
+		validate              func(*testing.T, []mcp.JSONRPCNotification, mcp.JSONRPCMessage)
+	}{
+		{
+			name: "DeleteResources sends single notifications/resources/list_changed",
+			action: func(t *testing.T, server *MCPServer, notificationChannel chan mcp.JSONRPCNotification) {
+				err := server.RegisterSession(context.TODO(), &fakeSession{
+					sessionID:           "test",
+					notificationChannel: notificationChannel,
+					initialized:         true,
+				})
+				require.NoError(t, err)
+				server.AddResource(
+					mcp.Resource{
+						URI:  "test://test-resource-1",
+						Name: "Test Resource 1",
+					},
+					func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
+						return []mcp.ResourceContents{}, nil
+					},
+				)
+				server.DeleteResources("test://test-resource-1")
+			},
+			expectedNotifications: 2,
+			validate: func(t *testing.T, notifications []mcp.JSONRPCNotification, resourcesList mcp.JSONRPCMessage) {
+				// One for AddResource
+				assert.Equal(t, mcp.MethodNotificationResourcesListChanged, notifications[0].Method)
+				// One for DeleteResources
+				assert.Equal(t, mcp.MethodNotificationResourcesListChanged, notifications[1].Method)
+
+				// Expect a successful response with an empty list of resources
+				resp, ok := resourcesList.(mcp.JSONRPCResponse)
+				assert.True(t, ok, "Expected JSONRPCResponse, got %T", resourcesList)
+
+				result, ok := resp.Result.(mcp.ListResourcesResult)
+				assert.True(t, ok, "Expected ListResourcesResult, got %T", resp.Result)
+
+				assert.Empty(t, result.Resources, "Expected empty resources list")
+			},
+		},
+		{
+			name: "DeleteResources removes the first resource and retains the other",
+			action: func(t *testing.T, server *MCPServer, notificationChannel chan mcp.JSONRPCNotification) {
+				err := server.RegisterSession(context.TODO(), &fakeSession{
+					sessionID:           "test",
+					notificationChannel: notificationChannel,
+					initialized:         true,
+				})
+				require.NoError(t, err)
+				server.AddResource(
+					mcp.Resource{
+						URI:  "test://test-resource-1",
+						Name: "Test Resource 1",
+					},
+					func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
+						return []mcp.ResourceContents{}, nil
+					},
+				)
+				server.AddResource(
+					mcp.Resource{
+						URI:  "test://test-resource-2",
+						Name: "Test Resource 2",
+					},
+					func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
+						return []mcp.ResourceContents{}, nil
+					},
+				)
+				server.DeleteResources("test://test-resource-1")
+			},
+			expectedNotifications: 3,
+			validate: func(t *testing.T, notifications []mcp.JSONRPCNotification, resourcesList mcp.JSONRPCMessage) {
+				// first notification expected for AddResource test-resource-1
+				assert.Equal(t, mcp.MethodNotificationResourcesListChanged, notifications[0].Method)
+				// second notification expected for AddResource test-resource-2
+				assert.Equal(t, mcp.MethodNotificationResourcesListChanged, notifications[1].Method)
+				// third notification expected for DeleteResources test-resource-1
+				assert.Equal(t, mcp.MethodNotificationResourcesListChanged, notifications[2].Method)
+
+				// Confirm the resource list contains only test-resource-2
+				resources := resourcesList.(mcp.JSONRPCResponse).Result.(mcp.ListResourcesResult).Resources
+				assert.Len(t, resources, 1)
+				assert.Equal(t, "test://test-resource-2", resources[0].URI)
+			},
+		},
+		{
+			name: "DeleteResources with non-existent resources does nothing and not receives notifications from MCPServer",
+			action: func(t *testing.T, server *MCPServer, notificationChannel chan mcp.JSONRPCNotification) {
+				err := server.RegisterSession(context.TODO(), &fakeSession{
+					sessionID:           "test",
+					notificationChannel: notificationChannel,
+					initialized:         true,
+				})
+				require.NoError(t, err)
+				server.AddResource(
+					mcp.Resource{
+						URI:  "test://test-resource-1",
+						Name: "Test Resource 1",
+					},
+					func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
+						return []mcp.ResourceContents{}, nil
+					},
+				)
+				server.AddResource(
+					mcp.Resource{
+						URI:  "test://test-resource-2",
+						Name: "Test Resource 2",
+					},
+					func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
+						return []mcp.ResourceContents{}, nil
+					},
+				)
+				// Remove non-existing resources
+				server.DeleteResources("test://test-resource-3", "test://test-resource-4")
+			},
+			expectedNotifications: 2,
+			validate: func(t *testing.T, notifications []mcp.JSONRPCNotification, resourcesList mcp.JSONRPCMessage) {
+				// first notification expected for AddResource test-resource-1
+				assert.Equal(t, mcp.MethodNotificationResourcesListChanged, notifications[0].Method)
+				// second notification expected for AddResource test-resource-2
+				assert.Equal(t, mcp.MethodNotificationResourcesListChanged, notifications[1].Method)
+
+				// Confirm the resource list does not change
+				resources := resourcesList.(mcp.JSONRPCResponse).Result.(mcp.ListResourcesResult).Resources
+				assert.Len(t, resources, 2)
+				// Resources are sorted by name
+				assert.Equal(t, "test://test-resource-1", resources[0].URI)
+				assert.Equal(t, "test://test-resource-2", resources[1].URI)
+			},
+		},
+		{
+			name: "SetResources sends single notifications/resources/list_changed with one active session",
+			action: func(t *testing.T, server *MCPServer, notificationChannel chan mcp.JSONRPCNotification) {
+				err := server.RegisterSession(context.TODO(), &fakeSession{
+					sessionID:           "test",
+					notificationChannel: notificationChannel,
+					initialized:         true,
+				})
+				require.NoError(t, err)
+				server.SetResources(ServerResource{
+					Resource: mcp.Resource{
+						URI:  "test://test-resource-1",
+						Name: "Test Resource 1",
+					},
+					Handler: func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
+						return []mcp.ResourceContents{}, nil
+					},
+				}, ServerResource{
+					Resource: mcp.Resource{
+						URI:  "test://test-resource-2",
+						Name: "Test Resource 2",
+					},
+					Handler: func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
+						return []mcp.ResourceContents{}, nil
+					},
+				})
+			},
+			expectedNotifications: 1,
+			validate: func(t *testing.T, notifications []mcp.JSONRPCNotification, resourcesList mcp.JSONRPCMessage) {
+				assert.Equal(t, mcp.MethodNotificationResourcesListChanged, notifications[0].Method)
+				resources := resourcesList.(mcp.JSONRPCResponse).Result.(mcp.ListResourcesResult).Resources
+				assert.Len(t, resources, 2)
+				// Resources are sorted by name
+				assert.Equal(t, "test://test-resource-1", resources[0].URI)
+				assert.Equal(t, "test://test-resource-2", resources[1].URI)
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			ctx := context.Background()
+			server := NewMCPServer("test-server", "1.0.0", WithResourceCapabilities(true, true))
+			_ = server.HandleMessage(ctx, []byte(`{
+				"jsonrpc": "2.0",
+				"id": 1,
+				"method": "initialize"
+			}`))
+			notificationChannel := make(chan mcp.JSONRPCNotification, 100)
+			notifications := make([]mcp.JSONRPCNotification, 0)
+			tt.action(t, server, notificationChannel)
+			for done := false; !done; {
+				select {
+				case serverNotification := <-notificationChannel:
+					notifications = append(notifications, serverNotification)
+					if len(notifications) == tt.expectedNotifications {
+						done = true
+					}
+				case <-time.After(1 * time.Second):
+					done = true
+				}
+			}
+			assert.Len(t, notifications, tt.expectedNotifications)
+			resourcesList := server.HandleMessage(ctx, []byte(`{
+				"jsonrpc": "2.0",
+				"id": 1,
+				"method": "resources/list"
+			}`))
+			tt.validate(t, notifications, resourcesList)
+		})
+	}
+}
+
 func TestMCPServer_HandleInvalidMessages(t *testing.T) {
 	var errs []error
 	hooks := &Hooks{}
@@ -2071,3 +2321,549 @@ func TestMCPServer_ProtocolNegotiation(t *testing.T) {
 		})
 	}
 }
+
+func TestMCPServer_GetTool(t *testing.T) {
+	t.Run("ExistingTool", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		expectedTool := mcp.Tool{
+			Name:        "test-tool",
+			Description: "A test tool",
+			InputSchema: mcp.ToolInputSchema{
+				Type: "object",
+				Properties: map[string]any{
+					"input": map[string]any{
+						"type":        "string",
+						"description": "Test input",
+					},
+				},
+			},
+		}
+
+		expectedHandler := func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+			return &mcp.CallToolResult{
+				Content: []mcp.Content{
+					mcp.TextContent{
+						Type: "text",
+						Text: "test result",
+					},
+				},
+			}, nil
+		}
+
+		server.AddTool(expectedTool, expectedHandler)
+
+		tool := server.GetTool("test-tool")
+
+		assert.NotNil(t, tool)
+		assert.Equal(t, expectedTool, tool.Tool)
+		assert.NotNil(t, tool.Handler)
+	})
+
+	t.Run("NonExistingTool", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		tool := server.GetTool("non-existing-tool")
+
+		assert.Nil(t, tool) // GetTool returns nil when tool doesn't exist
+	})
+
+	t.Run("EmptyServer", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		tool := server.GetTool("any-tool")
+
+		assert.Nil(t, tool) // GetTool returns nil when no tools are registered
+	})
+
+	t.Run("MultipleToolsGetSpecific", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		tools := []mcp.Tool{
+			{Name: "tool1", Description: "First tool"},
+			{Name: "tool2", Description: "Second tool"},
+			{Name: "tool3", Description: "Third tool"},
+		}
+
+		// Add all tools
+		for _, tool := range tools {
+			server.AddTool(tool, nil)
+		}
+
+		// Get specific tool
+		tool := server.GetTool("tool2")
+
+		assert.NotNil(t, tool)
+		assert.Equal(t, "tool2", tool.Tool.Name)
+		assert.Equal(t, "Second tool", tool.Tool.Description)
+	})
+
+	t.Run("WithComplexToolSchema", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		complexTool := mcp.Tool{
+			Name:        "complex-tool",
+			Description: "A complex tool with detailed schema",
+			InputSchema: mcp.ToolInputSchema{
+				Type: "object",
+				Properties: map[string]any{
+					"stringParam": map[string]any{
+						"type":        "string",
+						"description": "A string parameter",
+						"enum":        []string{"option1", "option2", "option3"},
+					},
+					"numberParam": map[string]any{
+						"type":        "number",
+						"description": "A number parameter",
+						"minimum":     0,
+						"maximum":     100,
+					},
+				},
+				Required: []string{"stringParam", "numberParam"},
+			},
+			Annotations: mcp.ToolAnnotation{
+				Title:           "Complex Tool",
+				DestructiveHint: mcp.ToBoolPtr(true),
+			},
+		}
+
+		handler := func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+			return &mcp.CallToolResult{}, nil
+		}
+
+		server.AddTool(complexTool, handler)
+
+		tool := server.GetTool("complex-tool")
+
+		assert.NotNil(t, tool)
+		assert.Equal(t, complexTool, tool.Tool)
+		assert.NotNil(t, tool.Handler)
+
+		// Verify the complex schema is preserved
+		assert.Equal(t, "object", tool.Tool.InputSchema.Type)
+		assert.Contains(t, tool.Tool.InputSchema.Properties, "stringParam")
+		assert.Contains(t, tool.Tool.InputSchema.Properties, "numberParam")
+		assert.Equal(t, []string{"stringParam", "numberParam"}, tool.Tool.InputSchema.Required)
+
+		// Verify annotations
+		assert.Equal(t, "Complex Tool", tool.Tool.Annotations.Title)
+		assert.NotNil(t, tool.Tool.Annotations.DestructiveHint)
+		assert.True(t, *tool.Tool.Annotations.DestructiveHint)
+	})
+
+	t.Run("CaseSensitivity", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		expectedTool := mcp.Tool{
+			Name:        "Test-Tool",
+			Description: "A case-sensitive test tool",
+		}
+
+		server.AddTool(expectedTool, nil)
+
+		// Exact match should work
+		tool := server.GetTool("Test-Tool")
+		assert.NotNil(t, tool)
+		assert.Equal(t, "Test-Tool", tool.Tool.Name)
+
+		// Different case should not match
+		tool = server.GetTool("test-tool")
+		assert.Nil(t, tool)
+
+		tool = server.GetTool("TEST-TOOL")
+		assert.Nil(t, tool)
+	})
+
+	t.Run("ConcurrentAccess", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		// Add some tools for concurrent access testing
+		for i := 0; i < 10; i++ {
+			server.AddTool(mcp.Tool{
+				Name:        fmt.Sprintf("tool-%d", i),
+				Description: fmt.Sprintf("Tool %d", i),
+			}, nil)
+		}
+
+		numGoroutines := 100
+		results := make(chan *ServerTool, numGoroutines)
+		var wg sync.WaitGroup
+
+		// Test concurrent reads
+		for i := 0; i < numGoroutines; i++ {
+			wg.Add(1)
+			go func(id int) {
+				defer wg.Done()
+				toolName := fmt.Sprintf("tool-%d", id%10) // Access tools cyclically
+				tool := server.GetTool(toolName)
+				results <- tool
+			}(i)
+		}
+
+		wg.Wait()
+		close(results)
+
+		// Verify all results are valid
+		validResults := 0
+		for tool := range results {
+			if tool != nil {
+				validResults++
+				assert.Contains(t, tool.Tool.Name, "tool-")
+			}
+		}
+
+		// All results should be valid since we're accessing existing tools
+		assert.Equal(t, numGoroutines, validResults)
+	})
+
+	t.Run("ReturnsCopyNotReference", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		originalTool := mcp.Tool{
+			Name:        "test-tool",
+			Description: "Original description",
+		}
+
+		server.AddTool(originalTool, nil)
+
+		// Get the tool twice
+		tool1 := server.GetTool("test-tool")
+		tool2 := server.GetTool("test-tool")
+
+		assert.NotNil(t, tool1)
+		assert.NotNil(t, tool2)
+
+		// Both should have the same content
+		assert.Equal(t, tool1.Tool, tool2.Tool)
+
+		// But they should be different memory addresses (copies)
+		assert.NotSame(t, tool1, tool2, "GetTool should return copies, not shared references")
+
+		// Modifying one should not affect the other or the server's copy
+		tool1.Tool.Description = "Modified description"
+
+		tool3 := server.GetTool("test-tool")
+		assert.NotNil(t, tool3)
+		assert.Equal(t, "Original description", tool3.Tool.Description, "Server's copy should be unchanged")
+		assert.Equal(t, "Original description", tool2.Tool.Description, "Other copy should be unchanged")
+	})
+
+	t.Run("EmptyToolName", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		// Add a tool with non-empty name
+		server.AddTool(mcp.Tool{Name: "valid-tool", Description: "Valid tool"}, nil)
+
+		// Request with empty string should return nil
+		tool := server.GetTool("")
+		assert.Nil(t, tool)
+	})
+
+	t.Run("NilAfterToolDeletion", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		// Add a tool
+		server.AddTool(mcp.Tool{Name: "temp-tool", Description: "Temporary tool"}, nil)
+
+		// Verify it exists
+		tool := server.GetTool("temp-tool")
+		assert.NotNil(t, tool)
+
+		// Delete the tool
+		server.DeleteTools("temp-tool")
+
+		// Now it should return nil
+		tool = server.GetTool("temp-tool")
+		assert.Nil(t, tool)
+	})
+}
+
+func TestMCPServer_ListTools(t *testing.T) {
+	t.Run("EmptyServer", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		tools := server.ListTools()
+
+		assert.Nil(t, tools)
+	})
+
+	t.Run("SingleTool", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		expectedTool := mcp.Tool{
+			Name:        "test-tool",
+			Description: "A test tool",
+			InputSchema: mcp.ToolInputSchema{
+				Type: "object",
+				Properties: map[string]any{
+					"input": map[string]any{
+						"type":        "string",
+						"description": "Test input",
+					},
+				},
+			},
+		}
+
+		expectedHandler := func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+			return &mcp.CallToolResult{
+				Content: []mcp.Content{
+					mcp.TextContent{
+						Type: "text",
+						Text: "test result",
+					},
+				},
+			}, nil
+		}
+
+		server.AddTool(expectedTool, expectedHandler)
+
+		tools := server.ListTools()
+
+		assert.NotNil(t, tools)
+		assert.Len(t, tools, 1)
+
+		serverTool, exists := tools["test-tool"]
+		assert.True(t, exists)
+		assert.Equal(t, expectedTool, serverTool.Tool)
+		assert.NotNil(t, serverTool.Handler)
+	})
+
+	t.Run("MultipleTools", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		tools := []struct {
+			tool    mcp.Tool
+			handler ToolHandlerFunc
+		}{
+			{
+				tool: mcp.Tool{
+					Name:        "tool1",
+					Description: "First tool",
+					InputSchema: mcp.ToolInputSchema{Type: "object"},
+				},
+				handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+					return &mcp.CallToolResult{}, nil
+				},
+			},
+			{
+				tool: mcp.Tool{
+					Name:        "tool2",
+					Description: "Second tool",
+					InputSchema: mcp.ToolInputSchema{Type: "object"},
+				},
+				handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+					return &mcp.CallToolResult{}, nil
+				},
+			},
+			{
+				tool: mcp.Tool{
+					Name:        "tool3",
+					Description: "Third tool",
+					InputSchema: mcp.ToolInputSchema{Type: "object"},
+				},
+				handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+					return &mcp.CallToolResult{}, nil
+				},
+			},
+		}
+
+		// Add tools one by one
+		for _, tool := range tools {
+			server.AddTool(tool.tool, tool.handler)
+		}
+
+		retrievedTools := server.ListTools()
+
+		assert.NotNil(t, retrievedTools)
+		assert.Len(t, retrievedTools, 3)
+
+		// Verify each tool exists with correct data
+		for _, expectedTool := range tools {
+			serverTool, exists := retrievedTools[expectedTool.tool.Name]
+			assert.True(t, exists, "Tool %s should exist", expectedTool.tool.Name)
+			assert.Equal(t, expectedTool.tool, serverTool.Tool)
+			assert.NotNil(t, serverTool.Handler)
+		}
+	})
+
+	t.Run("AfterToolDeletion", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		// Add multiple tools
+		server.AddTool(mcp.Tool{Name: "tool1", Description: "Tool 1"}, nil)
+		server.AddTool(mcp.Tool{Name: "tool2", Description: "Tool 2"}, nil)
+		server.AddTool(mcp.Tool{Name: "tool3", Description: "Tool 3"}, nil)
+
+		// Verify all tools exist
+		tools := server.ListTools()
+		assert.NotNil(t, tools)
+		assert.Len(t, tools, 3)
+
+		// Delete one tool
+		server.DeleteTools("tool2")
+
+		// Verify tool is removed
+		tools = server.ListTools()
+		assert.NotNil(t, tools)
+		assert.Len(t, tools, 2)
+
+		_, exists := tools["tool1"]
+		assert.True(t, exists)
+		_, exists = tools["tool2"]
+		assert.False(t, exists)
+		_, exists = tools["tool3"]
+		assert.True(t, exists)
+	})
+
+	t.Run("SetToolsReplacesExisting", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		// Add initial tools
+		server.AddTool(mcp.Tool{Name: "old-tool1", Description: "Old Tool 1"}, nil)
+		server.AddTool(mcp.Tool{Name: "old-tool2", Description: "Old Tool 2"}, nil)
+
+		// Verify initial tools
+		tools := server.ListTools()
+		assert.NotNil(t, tools)
+		assert.Len(t, tools, 2)
+
+		// Set new tools (should replace existing)
+		newTools := []ServerTool{
+			{
+				Tool: mcp.Tool{Name: "new-tool1", Description: "New Tool 1"},
+				Handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+					return &mcp.CallToolResult{}, nil
+				},
+			},
+			{
+				Tool: mcp.Tool{Name: "new-tool2", Description: "New Tool 2"},
+				Handler: func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+					return &mcp.CallToolResult{}, nil
+				},
+			},
+		}
+		server.SetTools(newTools...)
+
+		// Verify only new tools exist
+		tools = server.ListTools()
+		assert.NotNil(t, tools)
+		assert.Len(t, tools, 2)
+
+		_, exists := tools["old-tool1"]
+		assert.False(t, exists)
+		_, exists = tools["old-tool2"]
+		assert.False(t, exists)
+		_, exists = tools["new-tool1"]
+		assert.True(t, exists)
+		_, exists = tools["new-tool2"]
+		assert.True(t, exists)
+	})
+
+	t.Run("ConcurrentAccess", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		// Pre-add some tools to test concurrent reads
+		for i := 0; i < 50; i++ {
+			server.AddTool(mcp.Tool{
+				Name:        fmt.Sprintf("pre-tool-%d", i),
+				Description: fmt.Sprintf("Pre-added tool %d", i),
+			}, nil)
+		}
+
+		numGoroutines := 100
+		results := make(chan map[string]*ServerTool, numGoroutines)
+		var wg sync.WaitGroup
+
+		// Test concurrent reads (no race conditions in test logic)
+		for i := 0; i < numGoroutines; i++ {
+			wg.Add(1)
+			go func(id int) {
+				defer wg.Done()
+				tools := server.ListTools()
+				results <- tools
+			}(i)
+		}
+
+		wg.Wait()
+		close(results)
+
+		// Collect all results
+		var allResults []map[string]*ServerTool
+		for result := range results {
+			allResults = append(allResults, result)
+		}
+
+		// Verify that no data races occurred and all results are valid
+		for i, result := range allResults {
+			assert.NotNil(t, result, "Result %d should not be nil", i)
+			assert.Equal(t, 50, len(result), "All concurrent reads should return same number of tools")
+		}
+	})
+
+	t.Run("ConsistentResults", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		// Add a tool
+		server.AddTool(mcp.Tool{
+			Name:        "test-tool",
+			Description: "Test tool",
+		}, nil)
+
+		// Get tools multiple times
+		tools1 := server.ListTools()
+		tools2 := server.ListTools()
+		tools3 := server.ListTools()
+
+		assert.NotNil(t, tools1)
+		assert.NotNil(t, tools2)
+		assert.NotNil(t, tools3)
+
+		// All should have the same tool
+		assert.Len(t, tools1, 1)
+		assert.Len(t, tools2, 1)
+		assert.Len(t, tools3, 1)
+
+		assert.Contains(t, tools1, "test-tool")
+		assert.Contains(t, tools2, "test-tool")
+		assert.Contains(t, tools3, "test-tool")
+
+		// Verify tool content is the same
+		assert.Equal(t, tools1["test-tool"].Tool, tools2["test-tool"].Tool)
+		assert.Equal(t, tools2["test-tool"].Tool, tools3["test-tool"].Tool)
+	})
+
+	t.Run("ReturnsCopiesNotReferences", func(t *testing.T) {
+		server := NewMCPServer("test-server", "1.0.0")
+
+		// Add a tool
+		server.AddTool(mcp.Tool{
+			Name:        "test-tool",
+			Description: "Test tool",
+		}, nil)
+
+		// Get tools twice
+		tools1 := server.ListTools()
+		tools2 := server.ListTools()
+
+		assert.NotNil(t, tools1)
+		assert.NotNil(t, tools2)
+
+		// They should NOT be the same reference (different memory addresses)
+		// This verifies that ListTools returns copies, not shared references
+		if len(tools1) > 0 && len(tools2) > 0 {
+			assert.NotSame(t, tools1, tools2, "ListTools should return copies, not shared references")
+		}
+
+		// Modifying one should not affect the other
+		delete(tools1, "test-tool")
+		assert.Len(t, tools1, 0, "Modified copy should be empty")
+		assert.Len(t, tools2, 1, "Original copy should be unchanged")
+		assert.Contains(t, tools2, "test-tool", "Original copy should still contain the tool")
+
+		// Server should still have the tool
+		tools3 := server.ListTools()
+		assert.NotNil(t, tools3)
+		assert.Len(t, tools3, 1)
+		assert.Contains(t, tools3, "test-tool")
+	})
+}
diff --git a/server/session_test.go b/server/session_test.go
index 35f9b8d..0433448 100644
--- a/server/session_test.go
+++ b/server/session_test.go
@@ -902,7 +902,7 @@ func TestMCPServer_SessionToolCapabilitiesBehavior(t *testing.T) {
 		validateServer func(t *testing.T, s *MCPServer, session *sessionTestClientWithTools)
 	}{
 		{
-			name: "no tool capabilities provided",
+			name:          "no tool capabilities provided",
 			serverOptions: []ServerOption{
 				// No WithToolCapabilities
 			},
@@ -1471,8 +1471,8 @@ func TestMCPServer_LoggingNotificationFormat(t *testing.T) {
 	// Send log messages with different formats
 	testCases := []struct {
 		name     string
-		data     interface{}
-		expected interface{}
+		data     any
+		expected any
 	}{
 		{
 			name:     "string data",
@@ -1481,8 +1481,8 @@ func TestMCPServer_LoggingNotificationFormat(t *testing.T) {
 		},
 		{
 			name:     "structured data",
-			data:     map[string]interface{}{"key": "value", "num": 42},
-			expected: map[string]interface{}{"key": "value", "num": 42},
+			data:     map[string]any{"key": "value", "num": 42},
+			expected: map[string]any{"key": "value", "num": 42},
 		},
 		{
 			name:     "error data",
@@ -1514,9 +1514,9 @@ func TestMCPServer_LoggingNotificationFormat(t *testing.T) {
 				switch expected := tc.expected.(type) {
 				case string:
 					assert.Equal(t, expected, dataField)
-				case map[string]interface{}:
-					assert.IsType(t, map[string]interface{}{}, dataField)
-					dataMap := dataField.(map[string]interface{})
+				case map[string]any:
+					assert.IsType(t, map[string]any{}, dataField)
+					dataMap := dataField.(map[string]any)
 					for k, v := range expected {
 						assert.Equal(t, v, dataMap[k])
 					}
diff --git a/server/sse_test.go b/server/sse_test.go
index 2a2b03b..de8e29d 100644
--- a/server/sse_test.go
+++ b/server/sse_test.go
@@ -1257,7 +1257,7 @@ func TestSSEServer(t *testing.T) {
 			WithHooks(&Hooks{
 				OnAfterInitialize: []OnAfterInitializeFunc{
 					func(ctx context.Context, id any, message *mcp.InitializeRequest, result *mcp.InitializeResult) {
-						result.Meta = map[string]any{"invalid": func() {}} // marshal will fail
+						result.Meta = mcp.NewMetaFromMap(map[string]any{"invalid": func() {}}) // marshal will fail
 					},
 				},
 			}),
diff --git a/server/stdio_test.go b/server/stdio_test.go
index 4ec7259..8fb542a 100644
--- a/server/stdio_test.go
+++ b/server/stdio_test.go
@@ -4,10 +4,13 @@ import (
 	"bufio"
 	"context"
 	"encoding/json"
+	"fmt"
 	"io"
 	"log"
 	"os"
+	"sync"
 	"testing"
+	"time"
 
 	"github.com/mark3labs/mcp-go/mcp"
 )
@@ -267,4 +270,217 @@ func TestStdioServer(t *testing.T) {
 			t.Errorf("unexpected server error: %v", err)
 		}
 	})
+
+	t.Run("Can handle concurrent tool calls", func(t *testing.T) {
+		// Create pipes for stdin and stdout
+		stdinReader, stdinWriter := io.Pipe()
+		stdoutReader, stdoutWriter := io.Pipe()
+
+		// Track tool call executions (sync.Map is already thread-safe)
+		var callCount sync.Map
+
+		// Create server with test tools
+		mcpServer := NewMCPServer("test", "1.0.0")
+
+		// Add multiple tools that simulate work and track concurrent execution
+		for i := 0; i < 5; i++ {
+			toolName := fmt.Sprintf("test_tool_%d", i)
+			mcpServer.AddTool(
+				mcp.NewTool(toolName),
+				func(name string) func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+					return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+						// Track concurrent executions
+						count, _ := callCount.LoadOrStore(name, 0)
+						callCount.Store(name, count.(int)+1)
+
+						// Simulate some work
+						time.Sleep(10 * time.Millisecond)
+
+						return mcp.NewToolResultText(fmt.Sprintf("Result from %s", name)), nil
+					}
+				}(toolName),
+			)
+		}
+
+		stdioServer := NewStdioServer(mcpServer)
+		stdioServer.SetErrorLogger(log.New(io.Discard, "", 0))
+
+		// Create context with cancel
+		ctx, cancel := context.WithCancel(context.Background())
+		defer cancel()
+
+		// Start server
+		serverErrCh := make(chan error, 1)
+		go func() {
+			err := stdioServer.Listen(ctx, stdinReader, stdoutWriter)
+			if err != nil && err != io.EOF && err != context.Canceled {
+				serverErrCh <- err
+			}
+			stdoutWriter.Close()
+			close(serverErrCh)
+		}()
+
+		// Initialize the session
+		initRequest := map[string]any{
+			"jsonrpc": "2.0",
+			"id":      1,
+			"method":  "initialize",
+			"params": map[string]any{
+				"protocolVersion": "2024-11-05",
+				"clientInfo": map[string]any{
+					"name":    "test-client",
+					"version": "1.0.0",
+				},
+			},
+		}
+
+		requestBytes, _ := json.Marshal(initRequest)
+		if _, err := stdinWriter.Write(append(requestBytes, '\n')); err != nil {
+			t.Fatalf("Failed to write init request: %v", err)
+		}
+
+		// Read init response
+		scanner := bufio.NewScanner(stdoutReader)
+		scanner.Scan()
+
+		// Send multiple concurrent tool calls
+		var wg sync.WaitGroup
+		responseChan := make(chan string, 10)
+
+		// Send 10 concurrent tool calls
+		for i := 0; i < 10; i++ {
+			wg.Add(1)
+			go func(id int) {
+				defer wg.Done()
+
+				toolRequest := map[string]any{
+					"jsonrpc": "2.0",
+					"id":      id + 2,
+					"method":  "tools/call",
+					"params": map[string]any{
+						"name": fmt.Sprintf("test_tool_%d", id%5),
+					},
+				}
+
+				requestBytes, _ := json.Marshal(toolRequest)
+				if _, err := stdinWriter.Write(append(requestBytes, '\n')); err != nil {
+					t.Errorf("Failed to write tool request %d: %v", id, err)
+				}
+			}(i)
+		}
+
+		// Read all responses
+		go func() {
+			for i := 0; i < 10; i++ {
+				if scanner.Scan() {
+					responseChan <- scanner.Text()
+				}
+			}
+			close(responseChan)
+		}()
+
+		// Wait for all requests to be sent
+		wg.Wait()
+
+		// Collect responses
+		responses := make([]string, 0, 10)
+		timeout := time.After(2 * time.Second)
+
+	collectLoop:
+		for len(responses) < 10 {
+			select {
+			case resp, ok := <-responseChan:
+				if !ok {
+					break collectLoop
+				}
+				responses = append(responses, resp)
+			case <-timeout:
+				t.Fatal("Timeout waiting for responses")
+			}
+		}
+		// Verify we got all responses
+		if len(responses) != 10 {
+			t.Errorf("Expected 10 responses, got %d", len(responses))
+		}
+
+		// Verify no errors in responses
+		for _, resp := range responses {
+			var response map[string]any
+			if err := json.Unmarshal([]byte(resp), &response); err != nil {
+				t.Errorf("Failed to unmarshal response: %v", err)
+				continue
+			}
+
+			if response["error"] != nil {
+				t.Errorf("Unexpected error in response: %v", response["error"])
+			}
+
+			// Verify response has expected structure
+			if response["result"] == nil {
+				t.Error("Expected result in response")
+			}
+		}
+
+		// Verify tools were called
+		callCount.Range(func(key, value interface{}) bool {
+			toolName := key.(string)
+			count := value.(int)
+			if count == 0 {
+				t.Errorf("Tool %s was not called", toolName)
+			}
+			return true
+		})
+
+		// Clean up
+		cancel()
+		stdinWriter.Close()
+
+		// Check for server errors
+		if err := <-serverErrCh; err != nil {
+			t.Errorf("Server error: %v", err)
+		}
+	})
+
+	t.Run("Configuration options respect bounds", func(t *testing.T) {
+		mcpServer := NewMCPServer("test", "1.0.0")
+
+		// Test worker pool size bounds
+		stdioServer := NewStdioServer(mcpServer)
+		WithWorkerPoolSize(150)(stdioServer)
+		if stdioServer.workerPoolSize != 100 { // Should use maximum
+			t.Errorf("Expected maximum worker pool size 100, got %d", stdioServer.workerPoolSize)
+		}
+
+		// Test valid worker pool size
+		stdioServer = NewStdioServer(mcpServer)
+		WithWorkerPoolSize(50)(stdioServer)
+		if stdioServer.workerPoolSize != 50 {
+			t.Errorf("Expected worker pool size 50, got %d", stdioServer.workerPoolSize)
+		}
+
+		// Test queue size bounds
+		stdioServer = NewStdioServer(mcpServer)
+		WithQueueSize(20000)(stdioServer)
+		if stdioServer.queueSize != 10000 { // Should use maximum
+			t.Errorf("Expected maximum queue size 10000, got %d", stdioServer.queueSize)
+		}
+
+		// Test valid queue size
+		stdioServer = NewStdioServer(mcpServer)
+		WithQueueSize(500)(stdioServer)
+		if stdioServer.queueSize != 500 {
+			t.Errorf("Expected queue size 500, got %d", stdioServer.queueSize)
+		}
+
+		// Test zero and negative values
+		stdioServer = NewStdioServer(mcpServer)
+		WithWorkerPoolSize(0)(stdioServer)
+		WithQueueSize(-10)(stdioServer)
+		if stdioServer.workerPoolSize != 5 {
+			t.Errorf("Expected default worker pool size 5 for zero input, got %d", stdioServer.workerPoolSize)
+		}
+		if stdioServer.queueSize != 100 {
+			t.Errorf("Expected default queue size 100 for negative input, got %d", stdioServer.queueSize)
+		}
+	})
 }
diff --git a/server/streamable_http_sampling_test.go b/server/streamable_http_sampling_test.go
new file mode 100644
index 0000000..50be27f
--- /dev/null
+++ b/server/streamable_http_sampling_test.go
@@ -0,0 +1,216 @@
+package server
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/mark3labs/mcp-go/mcp"
+)
+
+// TestStreamableHTTPServer_SamplingBasic tests basic sampling session functionality
+func TestStreamableHTTPServer_SamplingBasic(t *testing.T) {
+	// Create MCP server with sampling enabled
+	mcpServer := NewMCPServer("test-server", "1.0.0")
+	mcpServer.EnableSampling()
+
+	// Create HTTP server
+	httpServer := NewStreamableHTTPServer(mcpServer)
+	testServer := httptest.NewServer(httpServer)
+	defer testServer.Close()
+
+	// Test session creation and interface implementation
+	sessionID := "test-session"
+	session := newStreamableHttpSession(sessionID, httpServer.sessionTools, httpServer.sessionLogLevels)
+
+	// Verify it implements SessionWithSampling
+	_, ok := any(session).(SessionWithSampling)
+	if !ok {
+		t.Error("streamableHttpSession should implement SessionWithSampling")
+	}
+
+	// Test that sampling request channels are initialized
+	if session.samplingRequestChan == nil {
+		t.Error("samplingRequestChan should be initialized")
+	}
+}
+
+// TestStreamableHTTPServer_SamplingErrorHandling tests error scenarios
+func TestStreamableHTTPServer_SamplingErrorHandling(t *testing.T) {
+	mcpServer := NewMCPServer("test-server", "1.0.0")
+	mcpServer.EnableSampling()
+
+	httpServer := NewStreamableHTTPServer(mcpServer)
+	testServer := httptest.NewServer(httpServer)
+	defer testServer.Close()
+
+	client := &http.Client{}
+	baseURL := testServer.URL
+
+	tests := []struct {
+		name           string
+		sessionID      string
+		body           map[string]any
+		expectedStatus int
+	}{
+		{
+			name:      "missing session ID",
+			sessionID: "",
+			body: map[string]any{
+				"jsonrpc": "2.0",
+				"id":      1,
+				"result": map[string]any{
+					"role": "assistant",
+					"content": map[string]any{
+						"type": "text",
+						"text": "Test response",
+					},
+				},
+			},
+			expectedStatus: http.StatusBadRequest,
+		},
+		{
+			name:      "invalid request ID",
+			sessionID: "mcp-session-550e8400-e29b-41d4-a716-446655440000",
+			body: map[string]any{
+				"jsonrpc": "2.0",
+				"id":      "invalid-id",
+				"result": map[string]any{
+					"role": "assistant",
+					"content": map[string]any{
+						"type": "text",
+						"text": "Test response",
+					},
+				},
+			},
+			expectedStatus: http.StatusBadRequest,
+		},
+		{
+			name:      "malformed result",
+			sessionID: "mcp-session-550e8400-e29b-41d4-a716-446655440000",
+			body: map[string]any{
+				"jsonrpc": "2.0",
+				"id":      1,
+				"result":  "invalid-result",
+			},
+			expectedStatus: http.StatusInternalServerError, // Now correctly returns 500 due to no active session
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			payload, _ := json.Marshal(tt.body)
+			req, err := http.NewRequest("POST", baseURL, bytes.NewReader(payload))
+			if err != nil {
+				t.Errorf("Failed to create request: %v", err)
+				return
+			}
+			req.Header.Set("Content-Type", "application/json")
+			if tt.sessionID != "" {
+				req.Header.Set("Mcp-Session-Id", tt.sessionID)
+			}
+
+			resp, err := client.Do(req)
+			if err != nil {
+				t.Errorf("Failed to send request: %v", err)
+				return
+			}
+			defer resp.Body.Close()
+
+			if resp.StatusCode != tt.expectedStatus {
+				t.Errorf("Expected status %d, got %d", tt.expectedStatus, resp.StatusCode)
+			}
+		})
+	}
+}
+
+// TestStreamableHTTPServer_SamplingInterface verifies interface implementation
+func TestStreamableHTTPServer_SamplingInterface(t *testing.T) {
+	mcpServer := NewMCPServer("test-server", "1.0.0")
+	mcpServer.EnableSampling()
+	httpServer := NewStreamableHTTPServer(mcpServer)
+	testServer := httptest.NewServer(httpServer)
+	defer testServer.Close()
+
+	// Create a session
+	sessionID := "test-session"
+	session := newStreamableHttpSession(sessionID, httpServer.sessionTools, httpServer.sessionLogLevels)
+
+	// Verify it implements SessionWithSampling
+	_, ok := any(session).(SessionWithSampling)
+	if !ok {
+		t.Error("streamableHttpSession should implement SessionWithSampling")
+	}
+
+	// Test RequestSampling with timeout
+	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
+	defer cancel()
+
+	request := mcp.CreateMessageRequest{
+		CreateMessageParams: mcp.CreateMessageParams{
+			Messages: []mcp.SamplingMessage{
+				{
+					Role: mcp.RoleUser,
+					Content: mcp.TextContent{
+						Type: "text",
+						Text: "Test message",
+					},
+				},
+			},
+		},
+	}
+
+	_, err := session.RequestSampling(ctx, request)
+	if err == nil {
+		t.Error("Expected timeout error, but got nil")
+	}
+
+	if !strings.Contains(err.Error(), "context deadline exceeded") {
+		t.Errorf("Expected timeout error, got: %v", err)
+	}
+}
+
+// TestStreamableHTTPServer_SamplingQueueFull tests queue overflow scenarios
+func TestStreamableHTTPServer_SamplingQueueFull(t *testing.T) {
+	sessionID := "test-session"
+	session := newStreamableHttpSession(sessionID, nil, nil)
+
+	// Fill the sampling request queue
+	for i := 0; i < cap(session.samplingRequestChan); i++ {
+		session.samplingRequestChan <- samplingRequestItem{
+			requestID: int64(i),
+			request:   mcp.CreateMessageRequest{},
+			response:  make(chan samplingResponseItem, 1),
+		}
+	}
+
+	// Try to add another request (should fail)
+	ctx := context.Background()
+	request := mcp.CreateMessageRequest{
+		CreateMessageParams: mcp.CreateMessageParams{
+			Messages: []mcp.SamplingMessage{
+				{
+					Role: mcp.RoleUser,
+					Content: mcp.TextContent{
+						Type: "text",
+						Text: "Test message",
+					},
+				},
+			},
+		},
+	}
+
+	_, err := session.RequestSampling(ctx, request)
+	if err == nil {
+		t.Error("Expected queue full error, but got nil")
+	}
+
+	if !strings.Contains(err.Error(), "queue is full") {
+		t.Errorf("Expected queue full error, got: %v", err)
+	}
+}
diff --git a/server/streamable_http_test.go b/server/streamable_http_test.go
index 5be010a..b464e1b 100644
--- a/server/streamable_http_test.go
+++ b/server/streamable_http_test.go
@@ -28,8 +28,7 @@ var initRequest = map[string]any{
 	"id":      1,
 	"method":  "initialize",
 	"params": map[string]any{
-		"protocolVersion": "2025-03-26",
-		"clientInfo": map[string]any{
+		"protocolVersion": mcp.LATEST_PROTOCOL_VERSION, "clientInfo": map[string]any{
 			"name":    "test-client",
 			"version": "1.0.0",
 		},
@@ -146,12 +145,12 @@ func TestStreamableHTTP_POST_SendAndReceive(t *testing.T) {
 		if err := json.Unmarshal(bodyBytes, &responseMessage); err != nil {
 			t.Fatalf("Failed to unmarshal response: %v", err)
 		}
-		if responseMessage.Result["protocolVersion"] != "2025-03-26" {
-			t.Errorf("Expected protocol version 2025-03-26, got %s", responseMessage.Result["protocolVersion"])
+		if responseMessage.Result["protocolVersion"] != mcp.LATEST_PROTOCOL_VERSION {
+			t.Errorf("Expected protocol version %s, got %s", mcp.LATEST_PROTOCOL_VERSION, responseMessage.Result["protocolVersion"])
 		}
 
 		// get session id from header
-		sessionID = resp.Header.Get(headerKeySessionID)
+		sessionID = resp.Header.Get(HeaderKeySessionID)
 		if sessionID == "" {
 			t.Fatalf("Expected session id in header, got %s", sessionID)
 		}
@@ -171,7 +170,7 @@ func TestStreamableHTTP_POST_SendAndReceive(t *testing.T) {
 			t.Fatalf("Failed to create request: %v", err)
 		}
 		req.Header.Set("Content-Type", "application/json")
-		req.Header.Set(headerKeySessionID, sessionID)
+		req.Header.Set(HeaderKeySessionID, sessionID)
 
 		resp, err := server.Client().Do(req)
 		if err != nil {
@@ -208,7 +207,7 @@ func TestStreamableHTTP_POST_SendAndReceive(t *testing.T) {
 			Notification: mcp.Notification{
 				Method: "testNotification",
 				Params: mcp.NotificationParams{
-					AdditionalFields: map[string]interface{}{"param1": "value1"},
+					AdditionalFields: map[string]any{"param1": "value1"},
 				},
 			},
 		}
@@ -216,7 +215,7 @@ func TestStreamableHTTP_POST_SendAndReceive(t *testing.T) {
 
 		req, _ := http.NewRequest(http.MethodPost, server.URL, bytes.NewBuffer(rawNotification))
 		req.Header.Set("Content-Type", "application/json")
-		req.Header.Set(headerKeySessionID, sessionID)
+		req.Header.Set(HeaderKeySessionID, sessionID)
 		resp, err := server.Client().Do(req)
 		if err != nil {
 			t.Fatalf("Failed to send message: %v", err)
@@ -246,7 +245,7 @@ func TestStreamableHTTP_POST_SendAndReceive(t *testing.T) {
 			t.Fatalf("Failed to create request: %v", err)
 		}
 		req.Header.Set("Content-Type", "application/json")
-		req.Header.Set(headerKeySessionID, "dummy-session-id")
+		req.Header.Set(HeaderKeySessionID, "dummy-session-id")
 
 		resp, err := server.Client().Do(req)
 		if err != nil {
@@ -275,7 +274,7 @@ func TestStreamableHTTP_POST_SendAndReceive(t *testing.T) {
 			t.Fatalf("Failed to create request: %v", err)
 		}
 		req.Header.Set("Content-Type", "application/json")
-		req.Header.Set(headerKeySessionID, sessionID)
+		req.Header.Set(HeaderKeySessionID, sessionID)
 
 		resp, err := server.Client().Do(req)
 		if err != nil {
@@ -339,12 +338,12 @@ func TestStreamableHTTP_POST_SendAndReceive_stateless(t *testing.T) {
 		if err := json.Unmarshal(bodyBytes, &responseMessage); err != nil {
 			t.Fatalf("Failed to unmarshal response: %v", err)
 		}
-		if responseMessage.Result["protocolVersion"] != "2025-03-26" {
-			t.Errorf("Expected protocol version 2025-03-26, got %s", responseMessage.Result["protocolVersion"])
+		if responseMessage.Result["protocolVersion"] != mcp.LATEST_PROTOCOL_VERSION {
+			t.Errorf("Expected protocol version %s, got %s", mcp.LATEST_PROTOCOL_VERSION, responseMessage.Result["protocolVersion"])
 		}
 
 		// no session id from header
-		sessionID := resp.Header.Get(headerKeySessionID)
+		sessionID := resp.Header.Get(HeaderKeySessionID)
 		if sessionID != "" {
 			t.Fatalf("Expected no session id in header, got %s", sessionID)
 		}
@@ -396,7 +395,7 @@ func TestStreamableHTTP_POST_SendAndReceive_stateless(t *testing.T) {
 			Notification: mcp.Notification{
 				Method: "testNotification",
 				Params: mcp.NotificationParams{
-					AdditionalFields: map[string]interface{}{"param1": "value1"},
+					AdditionalFields: map[string]any{"param1": "value1"},
 				},
 			},
 		}
@@ -433,7 +432,7 @@ func TestStreamableHTTP_POST_SendAndReceive_stateless(t *testing.T) {
 			t.Fatalf("Failed to create request: %v", err)
 		}
 		req.Header.Set("Content-Type", "application/json")
-		req.Header.Set(headerKeySessionID, "dummy-session-id")
+		req.Header.Set(HeaderKeySessionID, "dummy-session-id")
 
 		resp, err := server.Client().Do(req)
 		if err != nil {
@@ -473,7 +472,7 @@ func TestStreamableHTTP_POST_SendAndReceive_stateless(t *testing.T) {
 			t.Fatalf("Failed to create request: %v", err)
 		}
 		req.Header.Set("Content-Type", "application/json")
-		req.Header.Set(headerKeySessionID, "mcp-session-2c44d701-fd50-44ce-92b8-dec46185a741")
+		req.Header.Set(HeaderKeySessionID, "mcp-session-2c44d701-fd50-44ce-92b8-dec46185a741")
 
 		resp, err := server.Client().Do(req)
 		if err != nil {
@@ -565,8 +564,7 @@ func TestStreamableHTTP_HttpHandler(t *testing.T) {
 			"id":      1,
 			"method":  "initialize",
 			"params": map[string]any{
-				"protocolVersion": "2025-03-26",
-				"clientInfo": map[string]any{
+				"protocolVersion": mcp.LATEST_PROTOCOL_VERSION, "clientInfo": map[string]any{
 					"name":    "test-client",
 					"version": "1.0.0",
 				},
@@ -586,8 +584,8 @@ func TestStreamableHTTP_HttpHandler(t *testing.T) {
 		if err := json.Unmarshal(bodyBytes, &responseMessage); err != nil {
 			t.Fatalf("Failed to unmarshal response: %v", err)
 		}
-		if responseMessage.Result["protocolVersion"] != "2025-03-26" {
-			t.Errorf("Expected protocol version 2025-03-26, got %s", responseMessage.Result["protocolVersion"])
+		if responseMessage.Result["protocolVersion"] != mcp.LATEST_PROTOCOL_VERSION {
+			t.Errorf("Expected protocol version %s, got %s", mcp.LATEST_PROTOCOL_VERSION, responseMessage.Result["protocolVersion"])
 		}
 	})
 }
@@ -749,7 +747,7 @@ func TestStreamableHTTP_SessionWithLogging(t *testing.T) {
 			t.Fatalf("Failed to send init request: %v", err)
 		}
 		defer initResp.Body.Close()
-		sessionID := initResp.Header.Get(headerKeySessionID)
+		sessionID := initResp.Header.Get(HeaderKeySessionID)
 		if sessionID == "" {
 			t.Fatal("Expected session id in header")
 		}
@@ -769,7 +767,7 @@ func TestStreamableHTTP_SessionWithLogging(t *testing.T) {
 			t.Fatalf("Failed to create request: %v", err)
 		}
 		req.Header.Set("Content-Type", "application/json")
-		req.Header.Set(headerKeySessionID, sessionID)
+		req.Header.Set(HeaderKeySessionID, sessionID)
 
 		resp, err := testServer.Client().Do(req)
 		if err != nil {
@@ -844,56 +842,76 @@ func TestStreamableHTTPServer_WithOptions(t *testing.T) {
 }
 
 func TestStreamableHTTP_HeaderPassthrough(t *testing.T) {
-    mcpServer := NewMCPServer("test-mcp-server", "1.0")
-    
-    var receivedHeaders struct {
-        contentType string
-        customHeader string
-    }
-    mcpServer.AddTool(
-        mcp.NewTool("check-headers"),
-        func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
-            receivedHeaders.contentType = request.Header.Get("Content-Type")
-            receivedHeaders.customHeader = request.Header.Get("X-Custom-Header")
-            return mcp.NewToolResultText("ok"), nil
-        },
-    )
-
-    server := NewTestStreamableHTTPServer(mcpServer)
-    defer server.Close()
-
-    // Initialize to get session
-    resp, _ := postJSON(server.URL, initRequest)
-    sessionID := resp.Header.Get(headerKeySessionID)
-    resp.Body.Close()
-
-    // Test header passthrough
-    toolRequest := map[string]any{
-        "jsonrpc": "2.0",
-        "id":      2,
-        "method":  "tools/call",
-        "params": map[string]any{
-            "name": "check-headers",
-        },
-    }
-    toolBody, _ := json.Marshal(toolRequest)
-    req, _ := http.NewRequest("POST", server.URL, bytes.NewReader(toolBody))
-
-    const expectedContentType = "application/json"
-    const expectedCustomHeader = "test-value"
-    req.Header.Set("Content-Type", expectedContentType)
-    req.Header.Set("X-Custom-Header", expectedCustomHeader)
-    req.Header.Set(headerKeySessionID, sessionID)
-
-    resp, _ = server.Client().Do(req)
-    resp.Body.Close()
-
-    if receivedHeaders.contentType != expectedContentType {
-        t.Errorf("Expected Content-Type header '%s', got '%s'", expectedContentType, receivedHeaders.contentType)
-    }
-    if receivedHeaders.customHeader != expectedCustomHeader {
-        t.Errorf("Expected X-Custom-Header '%s', got '%s'", expectedCustomHeader, receivedHeaders.customHeader)
-    }
+	mcpServer := NewMCPServer("test-mcp-server", "1.0")
+
+	var receivedHeaders struct {
+		contentType  string
+		customHeader string
+	}
+	mcpServer.AddTool(
+		mcp.NewTool("check-headers"),
+		func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+			receivedHeaders.contentType = request.Header.Get("Content-Type")
+			receivedHeaders.customHeader = request.Header.Get("X-Custom-Header")
+			return mcp.NewToolResultText("ok"), nil
+		},
+	)
+
+	server := NewTestStreamableHTTPServer(mcpServer)
+	defer server.Close()
+
+	// Initialize to get session
+	resp, _ := postJSON(server.URL, initRequest)
+	sessionID := resp.Header.Get(HeaderKeySessionID)
+	resp.Body.Close()
+
+	// Test header passthrough
+	toolRequest := map[string]any{
+		"jsonrpc": "2.0",
+		"id":      2,
+		"method":  "tools/call",
+		"params": map[string]any{
+			"name": "check-headers",
+		},
+	}
+	toolBody, _ := json.Marshal(toolRequest)
+	req, _ := http.NewRequest("POST", server.URL, bytes.NewReader(toolBody))
+
+	const expectedContentType = "application/json"
+	const expectedCustomHeader = "test-value"
+	req.Header.Set("Content-Type", expectedContentType)
+	req.Header.Set("X-Custom-Header", expectedCustomHeader)
+	req.Header.Set(HeaderKeySessionID, sessionID)
+
+	resp, _ = server.Client().Do(req)
+	resp.Body.Close()
+
+	if receivedHeaders.contentType != expectedContentType {
+		t.Errorf("Expected Content-Type header '%s', got '%s'", expectedContentType, receivedHeaders.contentType)
+	}
+	if receivedHeaders.customHeader != expectedCustomHeader {
+		t.Errorf("Expected X-Custom-Header '%s', got '%s'", expectedCustomHeader, receivedHeaders.customHeader)
+	}
+}
+
+func TestStreamableHTTPServer_TLS(t *testing.T) {
+	t.Run("TLS options are set correctly", func(t *testing.T) {
+		mcpServer := NewMCPServer("test-mcp-server", "1.0.0")
+		certFile := "/path/to/cert.pem"
+		keyFile := "/path/to/key.pem"
+
+		server := NewStreamableHTTPServer(
+			mcpServer,
+			WithTLSCert(certFile, keyFile),
+		)
+
+		if server.tlsCertFile != certFile {
+			t.Errorf("Expected tlsCertFile to be %s, got %s", certFile, server.tlsCertFile)
+		}
+		if server.tlsKeyFile != keyFile {
+			t.Errorf("Expected tlsKeyFile to be %s, got %s", keyFile, server.tlsKeyFile)
+		}
+	})
 }
 
 func postJSON(url string, bodyObject any) (*http.Response, error) {
diff --git a/testdata/mockstdio_server.go b/testdata/mockstdio_server.go
index f561285..30bf0c0 100644
--- a/testdata/mockstdio_server.go
+++ b/testdata/mockstdio_server.go
@@ -52,7 +52,7 @@ func handleRequest(request JSONRPCRequest) JSONRPCResponse {
 	switch request.Method {
 	case "initialize":
 		response.Result = map[string]any{
-			"protocolVersion": "1.0",
+			"protocolVersion": mcp.LATEST_PROTOCOL_VERSION,
 			"serverInfo": map[string]any{
 				"name":    "mock-server",
 				"version": "1.0.0",