Repository: mark3labs/mcp-go

Issue:
Feature: Add Sampling Support
**Priority**: High  
**Breaking Change**: No

**Description**: Implement MCP sampling support to allow servers to request LLM sampling ("completions" or "generations") from language models via clients. This enables servers to implement agentic behaviors with nested LLM calls while maintaining client control over model access and permissions.

## Overview
The sampling feature allows servers to request text, audio, or image-based interactions from language models through the client, with no server API keys necessary. Clients maintain control over model selection, permissions, and user approval.

## Key Features
- **sampling/createMessage** request support
- Model preference system with capability priorities and hints
- Support for text, image, and audio content
- User approval controls and security considerations
- Rate limiting and sensitive data handling

## Implementation Tasks

### Core Implementation
- [ ] Add sampling capability declaration during initialization
- [ ] Implement `sampling/createMessage` request/response handling
- [ ] Add message content types (text, image, audio)
- [ ] Implement model preference system with priorities and hints
- [ ] Add proper error handling for sampling requests
- [ ] Implement security controls and user approval mechanisms

### Transport-Specific Tasks

#### STDIO Transport
- [ ] Implement sampling support in STDIO client transport
- [ ] Add bidirectional message handling for sampling requests
- [ ] Test sampling flow with STDIO server examples
- [ ] Add STDIO-specific error handling for sampling

#### Streamable HTTP Transport  
- [ ] Implement sampling support in Streamable HTTP client transport
- [ ] Add HTTP-specific headers and request handling for sampling
- [ ] Test sampling flow with HTTP server examples
- [ ] Add HTTP-specific error handling and status codes for sampling

### Data Types and Validation
- [ ] Add `SamplingMessage` types with role and content fields
- [ ] Add `ModelPreferences` struct with priorities and hints
- [ ] Add `SamplingResult` struct with model and stop reason
- [ ] Implement content validation for text, image, and audio
- [ ] Add JSON schema validation for sampling requests

### Security and User Experience
- [ ] Implement user approval UI/controls for sampling requests
- [ ] Add prompt review and editing capabilities
- [ ] Add response review before delivery
- [ ] Implement rate limiting for sampling requests
- [ ] Add logging and audit trail for sampling operations

### Testing and Documentation
- [ ] Add unit tests for sampling message types
- [ ] Add integration tests for both transport types
- [ ] Add examples demonstrating sampling usage
- [ ] Update documentation with sampling capabilities
- [ ] Add security best practices documentation

## Files to Update
- `mcp/types.go` - Add sampling message types
- `client/interface.go` - Add sampling methods
- `client/stdio.go` - STDIO sampling implementation
- `client/http.go` - HTTP sampling implementation  
- `server/server.go` - Server-side sampling support
- `examples/` - Add sampling examples

## Security Considerations
1. Clients SHOULD implement user approval controls
2. Both parties SHOULD validate message content
3. Clients SHOULD respect model preference hints
4. Clients SHOULD implement rate limiting
5. Both parties MUST handle sensitive data appropriately

## Reference
Based on MCP Specification 2025-06-18: https://modelcontextprotocol.io/specification/2025-06-18/client/sampling

Hints from issue comments:
@andig: Seems this has been implemented, at least partially?@ezynda3: Only stdio and inprocess work at the moment @andig: It seems `EnableSampling()` is currently a nop. Is this really needed as additional API or shouldn't we just allow servers to use sampling?

Solution patch:
diff --git a/client/client.go b/client/client.go
index e2c4665..6990ae1 100644
--- a/client/client.go
+++ b/client/client.go
@@ -39,7 +39,7 @@ func WithClientCapabilities(capabilities mcp.ClientCapabilities) ClientOption {
 func WithSamplingHandler(handler SamplingHandler) ClientOption {
 	return func(c *Client) {
 		c.samplingHandler = handler
-  }
+	}
 }
 
 // WithSession assumes a MCP Session has already been initialized
@@ -133,7 +133,7 @@ func (c *Client) sendRequest(
 
 	response, err := c.transport.SendRequest(ctx, request)
 	if err != nil {
-		return nil, fmt.Errorf("transport error: %w", err)
+		return nil, transport.NewError(err)
 	}
 
 	if response.Error != nil {
diff --git a/client/inprocess.go b/client/inprocess.go
index 5d8559d..62d2879 100644
--- a/client/inprocess.go
+++ b/client/inprocess.go
@@ -1,7 +1,10 @@
 package client
 
 import (
+	"context"
+
 	"github.com/mark3labs/mcp-go/client/transport"
+	"github.com/mark3labs/mcp-go/mcp"
 	"github.com/mark3labs/mcp-go/server"
 )
 
@@ -10,3 +13,26 @@ func NewInProcessClient(server *server.MCPServer) (*Client, error) {
 	inProcessTransport := transport.NewInProcessTransport(server)
 	return NewClient(inProcessTransport), nil
 }
+
+// NewInProcessClientWithSamplingHandler creates an in-process client with sampling support
+func NewInProcessClientWithSamplingHandler(server *server.MCPServer, handler SamplingHandler) (*Client, error) {
+	// Create a wrapper that implements server.SamplingHandler
+	serverHandler := &inProcessSamplingHandlerWrapper{handler: handler}
+
+	inProcessTransport := transport.NewInProcessTransportWithOptions(server,
+		transport.WithSamplingHandler(serverHandler))
+
+	client := NewClient(inProcessTransport)
+	client.samplingHandler = handler
+
+	return client, nil
+}
+
+// inProcessSamplingHandlerWrapper wraps client.SamplingHandler to implement server.SamplingHandler
+type inProcessSamplingHandlerWrapper struct {
+	handler SamplingHandler
+}
+
+func (w *inProcessSamplingHandlerWrapper) CreateMessage(ctx context.Context, request mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) {
+	return w.handler.CreateMessage(ctx, request)
+}
diff --git a/client/transport/error.go b/client/transport/error.go
new file mode 100644
index 0000000..1f02994
--- /dev/null
+++ b/client/transport/error.go
@@ -0,0 +1,22 @@
+package transport
+
+import "fmt"
+
+// Error wraps a low-level transport error in a concrete type.
+type Error struct {
+	Err error
+}
+
+func (e *Error) Error() string {
+	return fmt.Sprintf("transport error: %v", e.Err)
+}
+
+func (e *Error) Unwrap() error {
+	return e.Err
+}
+
+func NewError(err error) *Error {
+	return &Error{
+		Err: err,
+	}
+}
diff --git a/client/transport/inprocess.go b/client/transport/inprocess.go
index 0e2393f..59c7094 100644
--- a/client/transport/inprocess.go
+++ b/client/transport/inprocess.go
@@ -11,19 +11,50 @@ import (
 )
 
 type InProcessTransport struct {
-	server *server.MCPServer
+	server          *server.MCPServer
+	samplingHandler server.SamplingHandler
+	session         *server.InProcessSession
+	sessionID       string
 
 	onNotification func(mcp.JSONRPCNotification)
 	notifyMu       sync.RWMutex
 }
 
+type InProcessOption func(*InProcessTransport)
+
+func WithSamplingHandler(handler server.SamplingHandler) InProcessOption {
+	return func(t *InProcessTransport) {
+		t.samplingHandler = handler
+	}
+}
+
 func NewInProcessTransport(server *server.MCPServer) *InProcessTransport {
 	return &InProcessTransport{
 		server: server,
 	}
 }
 
+func NewInProcessTransportWithOptions(server *server.MCPServer, opts ...InProcessOption) *InProcessTransport {
+	t := &InProcessTransport{
+		server:    server,
+		sessionID: server.GenerateInProcessSessionID(),
+	}
+
+	for _, opt := range opts {
+		opt(t)
+	}
+
+	return t
+}
+
 func (c *InProcessTransport) Start(ctx context.Context) error {
+	// Create and register session if we have a sampling handler
+	if c.samplingHandler != nil {
+		c.session = server.NewInProcessSession(c.sessionID, c.samplingHandler)
+		if err := c.server.RegisterSession(ctx, c.session); err != nil {
+			return fmt.Errorf("failed to register session: %w", err)
+		}
+	}
 	return nil
 }
 
@@ -34,6 +65,11 @@ func (c *InProcessTransport) SendRequest(ctx context.Context, request JSONRPCReq
 	}
 	requestBytes = append(requestBytes, '\n')
 
+	// Add session to context if available
+	if c.session != nil {
+		ctx = c.server.WithContext(ctx, c.session)
+	}
+
 	respMessage := c.server.HandleMessage(ctx, requestBytes)
 	respByte, err := json.Marshal(respMessage)
 	if err != nil {
@@ -65,7 +101,10 @@ func (c *InProcessTransport) SetNotificationHandler(handler func(notification mc
 	c.onNotification = handler
 }
 
-func (*InProcessTransport) Close() error {
+func (c *InProcessTransport) Close() error {
+	if c.session != nil {
+		c.server.UnregisterSession(context.Background(), c.sessionID)
+	}
 	return nil
 }
 
diff --git a/examples/inprocess_sampling/README.md b/examples/inprocess_sampling/README.md
new file mode 100644
index 0000000..7776fd5
--- /dev/null
+++ b/examples/inprocess_sampling/README.md
@@ -0,0 +1,39 @@
+# InProcess Sampling Example
+
+This example demonstrates how to use sampling with in-process MCP client/server communication.
+
+## Overview
+
+The example shows:
+- Creating an MCP server with sampling enabled
+- Adding a tool that uses sampling to request LLM completions
+- Creating an in-process client with a sampling handler
+- Making tool calls that trigger sampling requests
+
+## Key Components
+
+### Server Side
+- `mcpServer.EnableSampling()` - Enables sampling capability
+- Tool handler calls `mcpServer.RequestSampling()` to request LLM completions
+- Sampling requests are handled directly by the client's sampling handler
+
+### Client Side
+- `MockSamplingHandler` - Implements the `SamplingHandler` interface
+- `NewInProcessClientWithSamplingHandler()` - Creates client with sampling support
+- The handler receives sampling requests and returns mock LLM responses
+
+## Running the Example
+
+```bash
+go run main.go
+```
+
+## Expected Output
+
+```
+Tool result: LLM Response (model: mock-llm-v1): Mock LLM response to: 'What is the capital of France?'
+```
+
+## Real LLM Integration
+
+To integrate with a real LLM service (OpenAI, Anthropic, etc.), replace the `MockSamplingHandler` with an implementation that calls your preferred LLM API. See the [client sampling documentation](https://mcp-go.dev/clients/advanced-sampling) for examples with real LLM providers.
\ No newline at end of file
diff --git a/examples/inprocess_sampling/main.go b/examples/inprocess_sampling/main.go
new file mode 100644
index 0000000..a50ee64
--- /dev/null
+++ b/examples/inprocess_sampling/main.go
@@ -0,0 +1,166 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"log"
+
+	"github.com/mark3labs/mcp-go/client"
+	"github.com/mark3labs/mcp-go/mcp"
+	"github.com/mark3labs/mcp-go/server"
+)
+
+// MockSamplingHandler implements client.SamplingHandler for demonstration
+type MockSamplingHandler struct{}
+
+func (h *MockSamplingHandler) CreateMessage(ctx context.Context, request mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) {
+	// Extract the user's message
+	var userMessage string
+	for _, msg := range request.Messages {
+		if msg.Role == mcp.RoleUser {
+			if textContent, ok := msg.Content.(mcp.TextContent); ok {
+				userMessage = textContent.Text
+				break
+			}
+		}
+	}
+
+	// Generate a mock response
+	mockResponse := fmt.Sprintf("Mock LLM response to: '%s'", userMessage)
+
+	return &mcp.CreateMessageResult{
+		SamplingMessage: mcp.SamplingMessage{
+			Role: mcp.RoleAssistant,
+			Content: mcp.TextContent{
+				Type: "text",
+				Text: mockResponse,
+			},
+		},
+		Model:      "mock-llm-v1",
+		StopReason: "endTurn",
+	}, nil
+}
+
+func main() {
+	// Create server with sampling enabled
+	mcpServer := server.NewMCPServer("inprocess-sampling-example", "1.0.0")
+	mcpServer.EnableSampling()
+
+	// Add a tool that uses sampling
+	mcpServer.AddTool(mcp.Tool{
+		Name:        "ask_llm",
+		Description: "Ask the LLM a question using sampling",
+		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",
+				},
+			},
+			Required: []string{"question"},
+		},
+	}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+		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 client
+		result, err := mcpServer.RequestSampling(ctx, 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
+		}
+
+		// Return the LLM response
+		return &mcp.CallToolResult{
+			Content: []mcp.Content{
+				mcp.TextContent{
+					Type: "text",
+					Text: fmt.Sprintf("LLM Response (model: %s): %s",
+						result.Model, result.Content.(mcp.TextContent).Text),
+				},
+			},
+		}, nil
+	})
+
+	// Create client with sampling handler
+	mockHandler := &MockSamplingHandler{}
+	mcpClient, err := client.NewInProcessClientWithSamplingHandler(mcpServer, mockHandler)
+	if err != nil {
+		log.Fatalf("Failed to create client: %v", err)
+	}
+	defer mcpClient.Close()
+
+	// Start the client
+	ctx := context.Background()
+	if err := mcpClient.Start(ctx); err != nil {
+		log.Fatalf("Failed to start client: %v", err)
+	}
+
+	// Initialize
+	initRequest := mcp.InitializeRequest{}
+	initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
+	initRequest.Params.ClientInfo = mcp.Implementation{
+		Name:    "inprocess-sampling-client",
+		Version: "1.0.0",
+	}
+
+	_, err = mcpClient.Initialize(ctx, initRequest)
+	if err != nil {
+		log.Fatalf("Failed to initialize: %v", err)
+	}
+
+	// Call the tool that uses sampling
+	result, err := mcpClient.CallTool(ctx, mcp.CallToolRequest{
+		Params: mcp.CallToolParams{
+			Name: "ask_llm",
+			Arguments: map[string]any{
+				"question":      "What is the capital of France?",
+				"system_prompt": "You are a helpful geography assistant.",
+			},
+		},
+	})
+	if err != nil {
+		log.Fatalf("Tool call failed: %v", err)
+	}
+
+	// Print the result
+	if len(result.Content) > 0 {
+		if textContent, ok := result.Content[0].(mcp.TextContent); ok {
+			fmt.Printf("Tool result: %s\n", textContent.Text)
+		}
+	}
+}
diff --git a/examples/sampling_client/sampling_client b/examples/sampling_client/sampling_client
deleted file mode 100755
index d18fa5f..0000000
Binary files a/examples/sampling_client/sampling_client and /dev/null differ
diff --git a/server/inprocess_session.go b/server/inprocess_session.go
new file mode 100644
index 0000000..fecf0aa
--- /dev/null
+++ b/server/inprocess_session.go
@@ -0,0 +1,101 @@
+package server
+
+import (
+	"context"
+	"fmt"
+	"sync"
+	"sync/atomic"
+	"time"
+
+	"github.com/mark3labs/mcp-go/mcp"
+)
+
+// SamplingHandler defines the interface for handling sampling requests from servers.
+type SamplingHandler interface {
+	CreateMessage(ctx context.Context, request mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error)
+}
+
+type InProcessSession struct {
+	sessionID       string
+	notifications   chan mcp.JSONRPCNotification
+	initialized     atomic.Bool
+	loggingLevel    atomic.Value
+	clientInfo      atomic.Value
+	samplingHandler SamplingHandler
+	mu              sync.RWMutex
+}
+
+func NewInProcessSession(sessionID string, samplingHandler SamplingHandler) *InProcessSession {
+	return &InProcessSession{
+		sessionID:       sessionID,
+		notifications:   make(chan mcp.JSONRPCNotification, 100),
+		samplingHandler: samplingHandler,
+	}
+}
+
+func (s *InProcessSession) SessionID() string {
+	return s.sessionID
+}
+
+func (s *InProcessSession) NotificationChannel() chan<- mcp.JSONRPCNotification {
+	return s.notifications
+}
+
+func (s *InProcessSession) Initialize() {
+	s.loggingLevel.Store(mcp.LoggingLevelError)
+	s.initialized.Store(true)
+}
+
+func (s *InProcessSession) Initialized() bool {
+	return s.initialized.Load()
+}
+
+func (s *InProcessSession) GetClientInfo() mcp.Implementation {
+	if value := s.clientInfo.Load(); value != nil {
+		if clientInfo, ok := value.(mcp.Implementation); ok {
+			return clientInfo
+		}
+	}
+	return mcp.Implementation{}
+}
+
+func (s *InProcessSession) SetClientInfo(clientInfo mcp.Implementation) {
+	s.clientInfo.Store(clientInfo)
+}
+
+func (s *InProcessSession) SetLogLevel(level mcp.LoggingLevel) {
+	s.loggingLevel.Store(level)
+}
+
+func (s *InProcessSession) GetLogLevel() mcp.LoggingLevel {
+	level := s.loggingLevel.Load()
+	if level == nil {
+		return mcp.LoggingLevelError
+	}
+	return level.(mcp.LoggingLevel)
+}
+
+func (s *InProcessSession) RequestSampling(ctx context.Context, request mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) {
+	s.mu.RLock()
+	handler := s.samplingHandler
+	s.mu.RUnlock()
+
+	if handler == nil {
+		return nil, fmt.Errorf("no sampling handler available")
+	}
+
+	return handler.CreateMessage(ctx, request)
+}
+
+// GenerateInProcessSessionID generates a unique session ID for inprocess clients
+func GenerateInProcessSessionID() string {
+	return fmt.Sprintf("inprocess-%d", time.Now().UnixNano())
+}
+
+// Ensure interface compliance
+var (
+	_ ClientSession         = (*InProcessSession)(nil)
+	_ SessionWithLogging    = (*InProcessSession)(nil)
+	_ SessionWithClientInfo = (*InProcessSession)(nil)
+	_ SessionWithSampling   = (*InProcessSession)(nil)
+)
diff --git a/server/sampling.go b/server/sampling.go
index b633b24..ae0812f 100644
--- a/server/sampling.go
+++ b/server/sampling.go
@@ -27,6 +27,11 @@ func (s *MCPServer) RequestSampling(ctx context.Context, request mcp.CreateMessa
 		return samplingSession.RequestSampling(ctx, request)
 	}
 
+	// Check for inprocess sampling handler in context
+	if handler := InProcessSamplingHandlerFromContext(ctx); handler != nil {
+		return handler.CreateMessage(ctx, request)
+	}
+
 	return nil, fmt.Errorf("session does not support sampling")
 }
 
@@ -35,3 +40,19 @@ type SessionWithSampling interface {
 	ClientSession
 	RequestSampling(ctx context.Context, request mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error)
 }
+
+// inProcessSamplingHandlerKey is the context key for storing inprocess sampling handler
+type inProcessSamplingHandlerKey struct{}
+
+// WithInProcessSamplingHandler adds a sampling handler to the context for inprocess clients
+func WithInProcessSamplingHandler(ctx context.Context, handler SamplingHandler) context.Context {
+	return context.WithValue(ctx, inProcessSamplingHandlerKey{}, handler)
+}
+
+// InProcessSamplingHandlerFromContext retrieves the inprocess sampling handler from context
+func InProcessSamplingHandlerFromContext(ctx context.Context) SamplingHandler {
+	if handler, ok := ctx.Value(inProcessSamplingHandlerKey{}).(SamplingHandler); ok {
+		return handler
+	}
+	return nil
+}
diff --git a/server/server.go b/server/server.go
index 46e6d9c..ee2eb9a 100644
--- a/server/server.go
+++ b/server/server.go
@@ -317,6 +317,11 @@ func NewMCPServer(
 	return s
 }
 
+// GenerateInProcessSessionID generates a unique session ID for inprocess clients
+func (s *MCPServer) GenerateInProcessSessionID() string {
+	return GenerateInProcessSessionID()
+}
+
 // AddResources registers multiple resources at once
 func (s *MCPServer) AddResources(resources ...ServerResource) {
 	s.implicitlyRegisterResourceCapabilities()
diff --git a/server/streamable_http.go b/server/streamable_http.go
index c21e381..6adf3cf 100644
--- a/server/streamable_http.go
+++ b/server/streamable_http.go
@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
+	"mime"
 	"net/http"
 	"net/http/httptest"
 	"strings"
@@ -213,7 +214,8 @@ func (s *StreamableHTTPServer) handlePost(w http.ResponseWriter, r *http.Request
 
 	// Check content type
 	contentType := r.Header.Get("Content-Type")
-	if contentType != "application/json" {
+	mediaType, _, err := mime.ParseMediaType(contentType)
+	if err != nil || mediaType != "application/json" {
 		http.Error(w, "Invalid content type: must be 'application/json'", http.StatusBadRequest)
 		return
 	}
diff --git a/www/docs/pages/clients/advanced-sampling.mdx b/www/docs/pages/clients/advanced-sampling.mdx
index 51172f1..81a4cc9 100644
--- a/www/docs/pages/clients/advanced-sampling.mdx
+++ b/www/docs/pages/clients/advanced-sampling.mdx
@@ -18,6 +18,7 @@ import (
     "fmt"
     
     "github.com/mark3labs/mcp-go/client"
+    "github.com/mark3labs/mcp-go/client/transport"
     "github.com/mark3labs/mcp-go/mcp"
 )
 
@@ -66,18 +67,18 @@ func main() {
     // Create sampling handler
     samplingHandler := &MySamplingHandler{}
     
+    // Create stdio transport
+    stdioTransport := transport.NewStdio("/path/to/mcp/server", nil)
+    
     // Create client with sampling support
-    mcpClient, err := client.NewStdioClient(
-        "/path/to/mcp/server",
-        client.WithSamplingHandler(samplingHandler),
-    )
-    if err != nil {
-        log.Fatalf("Failed to create client: %v", err)
-    }
-    defer mcpClient.Close()
+    mcpClient := client.NewClient(stdioTransport, client.WithSamplingHandler(samplingHandler))
     
-    // Connect to server
+    // Start the client
     ctx := context.Background()
+    if err := mcpClient.Start(ctx); err != nil {
+        log.Fatalf("Failed to start client: %v", err)
+    }
+    defer mcpClient.Close()
     if err := mcpClient.Connect(ctx); err != nil {
         log.Fatalf("Failed to connect: %v", err)
     }
@@ -150,18 +151,18 @@ func main() {
     
     serverPath := os.Args[1]
     
+    // Create stdio transport
+    stdioTransport := transport.NewStdio(serverPath, nil)
+    
     // Create client with mock sampling handler
-    mcpClient, err := client.NewStdioClient(
-        serverPath,
-        client.WithSamplingHandler(&MockSamplingHandler{}),
-    )
-    if err != nil {
-        log.Fatalf("Failed to create client: %v", err)
-    }
-    defer mcpClient.Close()
+    mcpClient := client.NewClient(stdioTransport, client.WithSamplingHandler(&MockSamplingHandler{}))
     
-    // Connect and test
+    // Start the client
     ctx := context.Background()
+    if err := mcpClient.Start(ctx); err != nil {
+        log.Fatalf("Failed to start client: %v", err)
+    }
+    defer mcpClient.Close()
     if err := mcpClient.Connect(ctx); err != nil {
         log.Fatalf("Failed to connect: %v", err)
     }
@@ -375,10 +376,8 @@ When you provide a sampling handler, the client automatically declares the sampl
 
 ```go
 // This automatically adds sampling capability
-mcpClient, err := client.NewStdioClient(
-    serverPath,
-    client.WithSamplingHandler(handler), // Enables sampling capability
-)
+stdioTransport := transport.NewStdio(serverPath, nil)
+mcpClient := client.NewClient(stdioTransport, client.WithSamplingHandler(handler))
 ```
 
 The client will include this in the initialization request:
@@ -460,8 +459,39 @@ go build -o my_client
 ./my_client ./sampling_server
 ```
 
+## Transport Support
+
+Sampling is available on the following transports:
+
+### STDIO Transport
+
+For STDIO clients, create the transport and client separately:
+
+```go
+stdioTransport := transport.NewStdio("/path/to/server", nil)
+mcpClient := client.NewClient(stdioTransport, client.WithSamplingHandler(&MySamplingHandler{}))
+```
+
+### In-Process Transport
+
+For in-process clients, use the dedicated constructor:
+
+```go
+mcpClient, err := client.NewInProcessClientWithSamplingHandler(
+    mcpServer, 
+    &MySamplingHandler{},
+)
+```
+
+In-process sampling uses direct method calls instead of JSON-RPC serialization.
+
+### Unsupported Transports
+
+SSE and StreamableHTTP transports do not support sampling due to their one-way or stateless nature.
+
 ## Next Steps
 
 - Learn about [server-side sampling implementation](/servers/advanced-sampling)
 - Explore [client operations](/clients/operations)
-- Check out the [sampling examples](https://github.com/mark3labs/mcp-go/tree/main/examples/sampling_client)
\ No newline at end of file
+- Check out the [sampling examples](https://github.com/mark3labs/mcp-go/tree/main/examples/sampling_client)
+- See [in-process transport documentation](/transports/inprocess) for embedded scenarios
\ No newline at end of file
diff --git a/www/docs/pages/servers/advanced-sampling.mdx b/www/docs/pages/servers/advanced-sampling.mdx
index 4f10ab9..1bc05eb 100644
--- a/www/docs/pages/servers/advanced-sampling.mdx
+++ b/www/docs/pages/servers/advanced-sampling.mdx
@@ -352,8 +352,48 @@ func getTextFromContent(content interface{}) string {
 }
 ```
 
+## Transport Support
+
+Sampling is supported on the following transports:
+
+### STDIO Transport
+
+STDIO transport provides full sampling support with JSON-RPC message passing:
+
+```go
+// Start STDIO server with sampling
+server.ServeStdio(mcpServer)
+```
+
+The client must implement a `SamplingHandler` and declare sampling capability during initialization.
+
+### In-Process Transport
+
+In-process transport offers the most efficient sampling implementation with direct method calls:
+
+```go
+// Create in-process client with sampling handler
+mcpClient, err := client.NewInProcessClientWithSamplingHandler(mcpServer, samplingHandler)
+if err != nil {
+    log.Fatal(err)
+}
+```
+
+**Benefits of in-process sampling:**
+- **Direct Method Calls**: No JSON-RPC serialization overhead
+- **Type Safety**: Compile-time type checking
+
+### Unsupported Transports
+
+The following transports do not currently support sampling:
+- **SSE Transport**: One-way streaming nature prevents bidirectional sampling
+- **StreamableHTTP Transport**: Stateless HTTP requests don't support sampling callbacks
+
+For these transports, consider implementing LLM integration directly in your tool handlers rather than using sampling.
+
 ## Next Steps
 
 - Learn about [client-side sampling implementation](/clients/advanced-sampling)
 - Explore [advanced server features](/servers/advanced)
-- Check out the [sampling examples](https://github.com/mark3labs/mcp-go/tree/main/examples/sampling_server)
\ No newline at end of file
+- Check out the [sampling examples](https://github.com/mark3labs/mcp-go/tree/main/examples/sampling_server)
+- See [in-process sampling documentation](/transports/inprocess#sampling-support) for embedded scenarios
\ No newline at end of file
diff --git a/www/docs/pages/transports/index.mdx b/www/docs/pages/transports/index.mdx
index 1150a4d..e8ec5ea 100644
--- a/www/docs/pages/transports/index.mdx
+++ b/www/docs/pages/transports/index.mdx
@@ -13,12 +13,12 @@ Transport layers handle the communication between MCP clients and servers. Each
 
 ## Transport Comparison
 
-| Transport | Use Case | Pros | Cons |
-|-----------|----------|------|------|
-| **STDIO** | CLI tools, desktop apps | Simple, secure, no network | Single client, local only |
-| **SSE** | Web apps, real-time | Multi-client, real-time, web-friendly | HTTP overhead, one-way streaming |
-| **StreamableHTTP** | Web services, APIs | Standard protocol, caching, load balancing | No real-time, more complex |
-| **In-Process** | Embedded, testing | No serialization, fastest | Same process only |
+| Transport | Use Case | Pros | Cons | Sampling Support |
+|-----------|----------|------|------|------------------|
+| **STDIO** | CLI tools, desktop apps | Simple, secure, no network | Single client, local only | ✅ Full support |
+| **SSE** | Web apps, real-time | Multi-client, real-time, web-friendly | HTTP overhead, one-way streaming | ❌ Not supported |
+| **StreamableHTTP** | Web services, APIs | Standard protocol, caching, load balancing | No real-time, more complex | ❌ Not supported |
+| **In-Process** | Embedded, testing | No serialization, fastest | Same process only | ✅ Full support |
 
 ## Quick Example
 
@@ -131,12 +131,14 @@ func handleEcho(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
 - Testing and development
 - High-performance scenarios
 - Library integrations
+- Sampling-enabled applications
 
 **Example use cases:**
 - Testing MCP implementations
 - Embedded analytics engines
 - High-frequency trading systems
 - Real-time game servers
+- LLM-powered applications with bidirectional communication
 
 ## Transport Configuration
 
diff --git a/www/docs/pages/transports/inprocess.mdx b/www/docs/pages/transports/inprocess.mdx
index bfdb153..e4b2c29 100644
--- a/www/docs/pages/transports/inprocess.mdx
+++ b/www/docs/pages/transports/inprocess.mdx
@@ -269,8 +269,356 @@ func main() {
 }
 ```
 
+## Sampling Support
+
+In-process transport supports sampling, allowing servers to request LLM completions from clients. This enables bidirectional communication where servers can leverage client-side LLM capabilities.
+
+### Enabling Sampling
+
+To enable sampling in your in-process server, call `EnableSampling()` during server setup:
+
+```go
+package main
+
+import (
+    "context"
+    "fmt"
+    "log"
+
+    "github.com/mark3labs/mcp-go/client"
+    "github.com/mark3labs/mcp-go/mcp"
+    "github.com/mark3labs/mcp-go/server"
+)
+
+// MockSamplingHandler implements client.SamplingHandler for demonstration
+type MockSamplingHandler struct{}
+
+func (h *MockSamplingHandler) CreateMessage(ctx context.Context, request mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) {
+    // Extract the user's message
+    var userMessage string
+    for _, msg := range request.Messages {
+        if msg.Role == mcp.RoleUser {
+            if textContent, ok := msg.Content.(mcp.TextContent); ok {
+                userMessage = textContent.Text
+                break
+            }
+        }
+    }
+
+    // Generate a mock response
+    mockResponse := fmt.Sprintf("Mock LLM response to: '%s'", userMessage)
+
+    return &mcp.CreateMessageResult{
+        SamplingMessage: mcp.SamplingMessage{
+            Role: mcp.RoleAssistant,
+            Content: mcp.TextContent{
+                Type: "text",
+                Text: mockResponse,
+            },
+        },
+        Model:      "mock-llm-v1",
+        StopReason: "endTurn",
+    }, nil
+}
+
+func main() {
+    // Create server with sampling enabled
+    mcpServer := server.NewMCPServer("sampling-server", "1.0.0")
+    mcpServer.EnableSampling()
+
+    // Add a tool that uses sampling
+    mcpServer.AddTool(mcp.Tool{
+        Name:        "ask_llm",
+        Description: "Ask the LLM a question using sampling",
+        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",
+                },
+            },
+            Required: []string{"question"},
+        },
+    }, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+        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 client
+        result, err := mcpServer.RequestSampling(ctx, 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
+        }
+
+        // Return the LLM response
+        return &mcp.CallToolResult{
+            Content: []mcp.Content{
+                mcp.TextContent{
+                    Type: "text",
+                    Text: fmt.Sprintf("LLM Response (model: %s): %s",
+                        result.Model, result.Content.(mcp.TextContent).Text),
+                },
+            },
+        }, nil
+    })
+
+    // Create client with sampling handler
+    mockHandler := &MockSamplingHandler{}
+    mcpClient, err := client.NewInProcessClientWithSamplingHandler(mcpServer, mockHandler)
+    if err != nil {
+        log.Fatalf("Failed to create client: %v", err)
+    }
+    defer mcpClient.Close()
+
+    // Start and initialize the client
+    ctx := context.Background()
+    if err := mcpClient.Start(ctx); err != nil {
+        log.Fatalf("Failed to start client: %v", err)
+    }
+
+    initRequest := mcp.InitializeRequest{}
+    initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
+    initRequest.Params.ClientInfo = mcp.Implementation{
+        Name:    "sampling-client",
+        Version: "1.0.0",
+    }
+
+    _, err = mcpClient.Initialize(ctx, initRequest)
+    if err != nil {
+        log.Fatalf("Failed to initialize: %v", err)
+    }
+
+    // Call the tool that uses sampling
+    result, err := mcpClient.CallTool(ctx, mcp.CallToolRequest{
+        Params: mcp.CallToolParams{
+            Name: "ask_llm",
+            Arguments: map[string]any{
+                "question":      "What is the capital of France?",
+                "system_prompt": "You are a helpful geography assistant.",
+            },
+        },
+    })
+    if err != nil {
+        log.Fatalf("Tool call failed: %v", err)
+    }
+
+    // Print the result
+    if len(result.Content) > 0 {
+        if textContent, ok := result.Content[0].(mcp.TextContent); ok {
+            fmt.Printf("Tool result: %s\n", textContent.Text)
+        }
+    }
+}
+```
+
+### Real LLM Integration
+
+For production use, replace the mock handler with a real LLM integration:
+
+#### OpenAI Integration
+
+```go
+import (
+    "github.com/sashabaranov/go-openai"
+)
+
+type OpenAISamplingHandler struct {
+    client *openai.Client
+}
+
+func NewOpenAISamplingHandler(apiKey string) *OpenAISamplingHandler {
+    return &OpenAISamplingHandler{
+        client: openai.NewClient(apiKey),
+    }
+}
+
+func (h *OpenAISamplingHandler) CreateMessage(ctx context.Context, request mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) {
+    // Convert MCP messages to OpenAI format
+    var messages []openai.ChatCompletionMessage
+
+    // Add system message if provided
+    if request.SystemPrompt != "" {
+        messages = append(messages, openai.ChatCompletionMessage{
+            Role:    openai.ChatMessageRoleSystem,
+            Content: request.SystemPrompt,
+        })
+    }
+
+    // Convert MCP messages
+    for _, msg := range request.Messages {
+        var role string
+        switch msg.Role {
+        case mcp.RoleUser:
+            role = openai.ChatMessageRoleUser
+        case mcp.RoleAssistant:
+            role = openai.ChatMessageRoleAssistant
+        }
+
+        if textContent, ok := msg.Content.(mcp.TextContent); ok {
+            messages = append(messages, openai.ChatCompletionMessage{
+                Role:    role,
+                Content: textContent.Text,
+            })
+        }
+    }
+
+    // Create OpenAI request
+    req := openai.ChatCompletionRequest{
+        Model:       openai.GPT3Dot5Turbo,
+        Messages:    messages,
+        MaxTokens:   request.MaxTokens,
+        Temperature: float32(request.Temperature),
+    }
+
+    // Call OpenAI API
+    resp, err := h.client.CreateChatCompletion(ctx, req)
+    if err != nil {
+        return nil, fmt.Errorf("OpenAI API call failed: %w", err)
+    }
+
+    if len(resp.Choices) == 0 {
+        return nil, fmt.Errorf("no response from OpenAI")
+    }
+
+    choice := resp.Choices[0]
+
+    // Convert stop reason
+    var stopReason string
+    switch choice.FinishReason {
+    case "stop":
+        stopReason = "endTurn"
+    case "length":
+        stopReason = "maxTokens"
+    default:
+        stopReason = "other"
+    }
+
+    return &mcp.CreateMessageResult{
+        SamplingMessage: mcp.SamplingMessage{
+            Role: mcp.RoleAssistant,
+            Content: mcp.TextContent{
+                Type: "text",
+                Text: choice.Message.Content,
+            },
+        },
+        Model:      resp.Model,
+        StopReason: stopReason,
+    }, nil
+}
+
+// Usage
+func main() {
+    // Create OpenAI handler
+    openaiHandler := NewOpenAISamplingHandler("your-api-key")
+    
+    // Create client with OpenAI sampling
+    mcpClient, err := client.NewInProcessClientWithSamplingHandler(mcpServer, openaiHandler)
+    if err != nil {
+        log.Fatal(err)
+    }
+    
+    // ... rest of the setup
+}
+```
+
+### Sampling Request Parameters
+
+The `CreateMessageRequest` supports various parameters to control LLM behavior:
+
+```go
+samplingRequest := mcp.CreateMessageRequest{
+    CreateMessageParams: mcp.CreateMessageParams{
+        // Required: Messages to send to the LLM
+        Messages: []mcp.SamplingMessage{
+            {
+                Role: mcp.RoleUser,        // or mcp.RoleAssistant
+                Content: mcp.TextContent{   // or mcp.ImageContent
+                    Type: "text",
+                    Text: "Your message here",
+                },
+            },
+        },
+        
+        // Optional: System prompt for context
+        SystemPrompt: "You are a helpful assistant.",
+        
+        // Optional: Maximum tokens to generate
+        MaxTokens: 1000,
+        
+        // Optional: Temperature for randomness (0.0 to 1.0)
+        Temperature: 0.7,
+        
+        // Optional: Top-p sampling parameter
+        TopP: 0.9,
+        
+        // Optional: Stop sequences
+        StopSequences: []string{"\\n\\n"},
+    },
+}
+```
+
+### Error Handling
+
+Always handle sampling errors gracefully:
+
+```go
+result, err := mcpServer.RequestSampling(ctx, samplingRequest)
+if err != nil {
+    // Log the error
+    log.Printf("Sampling request failed: %v", err)
+    
+    // Return appropriate error response
+    return &mcp.CallToolResult{
+        Content: []mcp.Content{
+            mcp.TextContent{
+                Type: "text",
+                Text: "Sorry, I couldn't process your request at this time.",
+            },
+        },
+        IsError: true,
+    }, nil
+}
+```
+
+
+
 ## Next Steps
 
 - **[Client Development](/clients)** - Build MCP clients for all transports
 - **[HTTP Transport](/transports/http)** - Learn about web-based scenarios
-- **[Server Advanced Features](/servers/advanced)** - Explore production-ready features
\ No newline at end of file
+- **[Server Advanced Features](/servers/advanced)** - Explore production-ready features
+- **[Client Sampling](/clients/advanced-sampling)** - Learn more about client-side sampling implementation
\ No newline at end of file


Test patch:
diff --git a/client/inprocess_sampling_test.go b/client/inprocess_sampling_test.go
new file mode 100644
index 0000000..087109e
--- /dev/null
+++ b/client/inprocess_sampling_test.go
@@ -0,0 +1,148 @@
+package client
+
+import (
+	"context"
+	"testing"
+
+	"github.com/mark3labs/mcp-go/mcp"
+	"github.com/mark3labs/mcp-go/server"
+)
+
+// MockSamplingHandler implements SamplingHandler for testing
+type MockSamplingHandler struct{}
+
+func (h *MockSamplingHandler) CreateMessage(ctx context.Context, request mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) {
+	return &mcp.CreateMessageResult{
+		SamplingMessage: mcp.SamplingMessage{
+			Role: mcp.RoleAssistant,
+			Content: mcp.TextContent{
+				Type: "text",
+				Text: "Mock response from sampling handler",
+			},
+		},
+		Model:      "mock-model",
+		StopReason: "endTurn",
+	}, nil
+}
+
+func TestInProcessSampling(t *testing.T) {
+	// Create server with sampling enabled
+	mcpServer := server.NewMCPServer("test-server", "1.0.0")
+	mcpServer.EnableSampling()
+
+	// Add a tool that uses sampling
+	mcpServer.AddTool(mcp.Tool{
+		Name:        "test_sampling",
+		Description: "Test sampling functionality",
+		InputSchema: mcp.ToolInputSchema{
+			Type: "object",
+			Properties: map[string]any{
+				"message": map[string]any{
+					"type":        "string",
+					"description": "Message to send to LLM",
+				},
+			},
+			Required: []string{"message"},
+		},
+	}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+		message, err := request.RequireString("message")
+		if err != nil {
+			return nil, err
+		}
+
+		// Create sampling request
+		samplingRequest := mcp.CreateMessageRequest{
+			CreateMessageParams: mcp.CreateMessageParams{
+				Messages: []mcp.SamplingMessage{
+					{
+						Role: mcp.RoleUser,
+						Content: mcp.TextContent{
+							Type: "text",
+							Text: message,
+						},
+					},
+				},
+				MaxTokens:   100,
+				Temperature: 0.7,
+			},
+		}
+
+		// Request sampling from client
+		result, err := mcpServer.RequestSampling(ctx, samplingRequest)
+		if err != nil {
+			return &mcp.CallToolResult{
+				Content: []mcp.Content{
+					mcp.TextContent{
+						Type: "text",
+						Text: "Sampling failed: " + err.Error(),
+					},
+				},
+				IsError: true,
+			}, nil
+		}
+
+		return &mcp.CallToolResult{
+			Content: []mcp.Content{
+				mcp.TextContent{
+					Type: "text",
+					Text: "Sampling result: " + result.Content.(mcp.TextContent).Text,
+				},
+			},
+		}, nil
+	})
+
+	// Create client with sampling handler
+	mockHandler := &MockSamplingHandler{}
+	client, err := NewInProcessClientWithSamplingHandler(mcpServer, mockHandler)
+	if err != nil {
+		t.Fatalf("Failed to create client: %v", err)
+	}
+	defer client.Close()
+
+	// Start the client
+	ctx := context.Background()
+	if err := client.Start(ctx); err != nil {
+		t.Fatalf("Failed to start client: %v", err)
+	}
+
+	// Initialize
+	initRequest := mcp.InitializeRequest{}
+	initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
+	initRequest.Params.ClientInfo = mcp.Implementation{
+		Name:    "test-client",
+		Version: "1.0.0",
+	}
+
+	_, err = client.Initialize(ctx, initRequest)
+	if err != nil {
+		t.Fatalf("Failed to initialize: %v", err)
+	}
+
+	// Call the tool that uses sampling
+	result, err := client.CallTool(ctx, mcp.CallToolRequest{
+		Params: mcp.CallToolParams{
+			Name: "test_sampling",
+			Arguments: map[string]any{
+				"message": "Hello, world!",
+			},
+		},
+	})
+	if err != nil {
+		t.Fatalf("Tool call failed: %v", err)
+	}
+
+	// Verify the result contains the mock response
+	if len(result.Content) == 0 {
+		t.Fatal("Expected content in result")
+	}
+
+	textContent, ok := result.Content[0].(mcp.TextContent)
+	if !ok {
+		t.Fatal("Expected text content")
+	}
+
+	expectedText := "Sampling result: Mock response from sampling handler"
+	if textContent.Text != expectedText {
+		t.Errorf("Expected %q, got %q", expectedText, textContent.Text)
+	}
+}