# MCP Sampling Implementation Guide
**Date**: January 11, 2026
**Feature**: Client-side LLM formatting via MCP Sampling
**Status**: Production-ready implementation
---
## ๐ฏ What is MCP Sampling?
**MCP Sampling** allows your MCP server to request the **client's LLM** to format/restructure responses. This means users can control how data is presented without changing your server code.
### Benefits:
- โ
**User controls formatting** (tables, JSON, bullets, CSV, etc.)
- โ
**Reusable across all tools** (DRY principle)
- โ
**Client-side processing** (reduces server load)
- โ
**Consistent with user preferences**
- โ
**No server changes needed** for new formats
---
## ๐๏ธ Architecture
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ USER REQUEST โ
โ "Show vendors in a table format" โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ YOUR MCP TOOL โ
โ 1. Fetch data from Laravel API โ
โ 2. Return raw data + sampling request โ
โ 3. No formatting logic needed! โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CLIENT LLM (Sampling) โ
โ 1. Receives raw data โ
โ 2. Formats based on user's request โ
โ 3. Returns formatted response โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ USER SEES โ
โ Beautifully formatted table/list/JSON โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
---
## ๐ Quick Start
### Option 1: Smart Formatting (Recommended)
The easiest way - automatically suggests best format based on data type:
```python
from src.core.sampling import create_smart_formatting_sampling
async def list_vendors_tool(...):
# Fetch your data
vendors = await fetch_vendors()
# Let client format it
return create_smart_formatting_sampling(
data=vendors,
data_type="vendor_list", # Suggests table format
user_preference=None # Or pass user's preference
)
```
### Option 2: Custom Formatting
Full control over formatting instructions:
```python
from src.core.sampling import create_sampling_request
async def list_vendors_tool(...):
vendors = await fetch_vendors()
return create_sampling_request(
data=vendors,
prompt="Format as a markdown table with columns: Name, Location, Manager, Status",
temperature=0.0 # Deterministic
)
```
### Option 3: Format Options
Let users choose from multiple formats:
```python
from src.core.sampling import create_format_options_sampling
async def list_vendors_tool(...):
vendors = await fetch_vendors()
return create_format_options_sampling(
data=vendors,
available_formats=["table", "json", "bullets", "csv"],
default_format="table"
)
```
---
## ๐ Real-World Examples
### Example 1: Vendor List with Different Formats
**User Request 1**: "Show me vendors"
```python
# Tool returns:
create_smart_formatting_sampling(
data={
"vendors": [
{"vendor_id": 1205, "vendor_name": "AutoAlert", "location": "Sai MP"},
{"vendor_id": 1206, "vendor_name": "Cardinal Forms", "location": "Sai MP"}
]
},
data_type="vendor_list"
)
# Client LLM formats as table (default):
| Name | Location | Manager | Status |
|----------------|----------|--------------|--------|
| AutoAlert | Sai MP | Mark One MP | Active |
| Cardinal Forms | Sai MP | - | Active |
```
**User Request 2**: "Show vendors as JSON"
```python
# Same tool, different user request
# Client LLM formats as JSON:
{
"vendors": [
{
"name": "AutoAlert",
"location": "Sai MP",
"manager": "Mark One MP",
"status": "Active"
}
]
}
```
**User Request 3**: "Show vendors as bullet points"
```python
# Same tool, different user request
# Client LLM formats as bullets:
โข AutoAlert
- Location: Sai MP
- Manager: Mark One MP
- Status: Active
โข Cardinal Forms
- Location: Sai MP
- Status: Active
```
### Example 2: Vendor Details with Sections
```python
from src.core.sampling import create_smart_formatting_sampling
async def get_vendor_details_tool(vendor_id: int, ...):
vendor = await fetch_vendor_details(vendor_id)
return create_smart_formatting_sampling(
data=vendor,
data_type="vendor_details" # Suggests sectioned format
)
# Client LLM formats with sections:
# ๐ Basic Information
# - Name: AutoAlert
# - DMS ID: AA12
# - Status: Active
#
# ๐ฅ Contacts
# - No contacts available
#
# ๐ Contracts
# - No contracts available
#
# ๐ Locations
# - Sai MP (Organization)
```
### Example 3: Contracts Summary
```python
async def list_contracts_tool(...):
contracts = await fetch_contracts()
return create_smart_formatting_sampling(
data=contracts,
data_type="contracts" # Suggests contract table format
)
# Client LLM formats as contract table:
| Contract Name | Status | Annual Amount | Start Date | End Date |
|---------------|--------|---------------|------------|------------|
| Service Agmt | Active | $50,000 | 2024-01-01 | 2024-12-31 |
| Maintenance | Active | $25,000 | 2024-03-01 | 2025-02-28 |
```
---
## ๐จ Supported Formats
### Built-in Format Helpers
```python
from src.core.sampling import (
format_as_table,
format_as_json,
format_as_bullets,
format_as_csv
)
# Table format
return format_as_table(
data=vendors,
columns=["Name", "Location", "Manager", "Status"]
)
# JSON format
return format_as_json(data=vendors, pretty=True)
# Bullet points
return format_as_bullets(data=vendors, hierarchical=True)
# CSV format
return format_as_csv(data=vendors)
```
### Custom Formats
```python
# Custom format with specific instructions
return create_sampling_request(
data=vendors,
prompt="""
Format this vendor data as:
1. A summary paragraph at the top
2. A table with key metrics
3. Bullet points for important notes
Use emojis for visual appeal.
""",
temperature=0.0
)
```
---
## ๐ง Integration with Existing Tools
### Before (Without Sampling)
```python
async def list_vendors_tool(...):
vendors = await fetch_vendors()
# You had to format the response yourself
response = "Here are the vendors:\n\n"
for v in vendors:
response += f"- {v['name']} ({v['location']})\n"
return {"response": response}
```
### After (With Sampling)
```python
async def list_vendors_tool(...):
vendors = await fetch_vendors()
# Just return raw data + sampling request
return create_smart_formatting_sampling(
data=vendors,
data_type="vendor_list"
)
```
**Benefits:**
- โ
Less code (DRY principle)
- โ
User controls format
- โ
Reusable across tools
- โ
No server changes for new formats
---
## ๐ฏ Best Practices
### 1. Always Include Raw Data
```python
# โ
GOOD: Include raw data for fallback
return create_sampling_request(
data=vendors,
prompt="Format as table"
)
# โ BAD: No raw data
return {
"sampling_request": {...}
# Missing raw_data!
}
```
### 2. Use Smart Formatting for Common Cases
```python
# โ
GOOD: Let smart formatting suggest best format
return create_smart_formatting_sampling(
data=vendors,
data_type="vendor_list"
)
# โ ๏ธ OK: Custom formatting for special cases
return create_sampling_request(
data=vendors,
prompt="Format as haiku poem" # Unusual format
)
```
### 3. Respect User Preferences
```python
# Track user's preferred format across requests
user_format_preference = get_user_preference(user_id)
return create_smart_formatting_sampling(
data=vendors,
data_type="vendor_list",
user_preference=user_format_preference # "table", "json", etc.
)
```
### 4. Use Appropriate Temperature
```python
# Deterministic formatting (recommended)
return create_sampling_request(
data=vendors,
prompt="Format as table",
temperature=0.0 # Same format every time
)
# Creative formatting (for summaries/descriptions)
return create_sampling_request(
data=vendors,
prompt="Write a brief summary",
temperature=0.3 # Slight variation
)
```
---
## ๐ How It Works Internally
### 1. Tool Returns Sampling Request
```python
{
"_meta": {
"sampling": True,
"description": "Client-side formatting requested"
},
"sampling_request": {
"messages": [
{
"role": "user",
"content": "Format this data as table:\n```json\n{...}\n```"
}
],
"modelPreferences": {
"temperature": 0.0,
"max_tokens": 2000
}
},
"raw_data": {...} # Fallback data
}
```
### 2. MCP Client Detects Sampling Request
```javascript
// Client-side (automatic)
if (response._meta?.sampling) {
// Send to client's LLM for formatting
const formatted = await clientLLM.complete(
response.sampling_request.messages,
response.sampling_request.modelPreferences
);
return formatted;
}
```
### 3. User Sees Formatted Response
The client's LLM formats the data and returns it to the user.
---
## ๐ Performance Considerations
### Server-side (Your MCP Server)
- โ
**Reduced load**: No formatting logic
- โ
**Faster responses**: Just return raw data
- โ
**Less code**: Reusable sampling module
### Client-side (User's LLM)
- โ ๏ธ **Extra LLM call**: One additional request
- โ ๏ธ **Latency**: ~1-2 seconds for formatting
- โ
**Cached**: Client can cache format preferences
### Trade-offs
- **Without Sampling**: Server does formatting, consistent but inflexible
- **With Sampling**: Client does formatting, flexible but slightly slower
**Recommendation**: Use sampling for user-facing responses where format matters.
---
## ๐งช Testing Sampling
### Test Script
```bash
#!/bin/bash
# test_sampling.sh
echo "Testing vendor list with different formats..."
# Test 1: Default format
curl -X POST http://localhost:8001/chat \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"message": "Show me vendors"}'
# Test 2: Table format
curl -X POST http://localhost:8001/chat \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"message": "Show vendors as a table"}'
# Test 3: JSON format
curl -X POST http://localhost:8001/chat \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"message": "Show vendors as JSON"}'
# Test 4: Bullet points
curl -X POST http://localhost:8001/chat \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"message": "Show vendors as bullet points"}'
```
---
## ๐ Production Deployment
### 1. Enable Sampling in Settings
```python
# src/config/settings.py
class Settings(BaseSettings):
# Sampling configuration
enable_sampling: bool = Field(
default=True,
description="Enable MCP sampling for client-side formatting"
)
sampling_max_tokens: int = Field(
default=2000,
description="Max tokens for sampling responses"
)
```
### 2. Add to .env
```env
# Sampling Configuration
ENABLE_SAMPLING=true
SAMPLING_MAX_TOKENS=2000
```
### 3. Monitor Usage
```python
# Track sampling requests
metrics.increment("sampling.requests")
metrics.histogram("sampling.response_size", len(data))
```
---
## ๐ Advanced Usage
### Dynamic Format Detection
```python
def detect_user_format_preference(message: str) -> Optional[str]:
"""Detect format from user's message."""
message_lower = message.lower()
if "table" in message_lower:
return "table"
elif "json" in message_lower:
return "json"
elif "bullet" in message_lower or "list" in message_lower:
return "bullets"
elif "csv" in message_lower:
return "csv"
return None
# In your tool:
user_format = detect_user_format_preference(user_message)
return create_smart_formatting_sampling(
data=vendors,
data_type="vendor_list",
user_preference=user_format
)
```
### Format Persistence
```python
# Store user's preferred format
async def save_user_format_preference(user_id: int, format: str):
await redis.set(f"user:{user_id}:format", format, ex=86400)
async def get_user_format_preference(user_id: int) -> Optional[str]:
return await redis.get(f"user:{user_id}:format")
# Use in tools:
user_format = await get_user_format_preference(user_context.user_id)
return create_smart_formatting_sampling(
data=vendors,
data_type="vendor_list",
user_preference=user_format
)
```
---
## ๐ Summary
### What You Get:
- โ
**Reusable sampling module** (`src/core/sampling.py`)
- โ
**Multiple format options** (table, JSON, bullets, CSV, custom)
- โ
**Smart formatting** (auto-suggests best format)
- โ
**Production-ready** (follows DRY and KISS principles)
- โ
**Easy integration** (one function call)
### How to Use:
1. Import sampling functions
2. Return sampling request instead of formatted string
3. Client's LLM handles formatting
4. User sees beautifully formatted response
### Key Benefits:
- **Users control formatting** without server changes
- **Reusable across all tools** (DRY principle)
- **Simple implementation** (KISS principle)
- **Production-ready** with monitoring and error handling
---
**Next Steps**: Integrate sampling into your vendor tools and test with different format requests!