Add example servers for SEP-2053 variant patterns (#4)

Add four new example servers demonstrating different SEP-2053 variant
patterns:

- model-optimized (same tools, different descriptions per LLM family)
- trading (API versioning with deprecation lifecycle)
- research (context budget management)
- Refactor the existing variants-http example into a GitHub-style server
with custom ranking.

Signed-off-by: Kurt Degiorgio <kdegiorgio@bloomberg.net>
This commit is contained in:
Kurt Degiorgio 2026-03-01 07:12:43 +00:00 • committed by GitHub
commit e1c17e047f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 1218 additions and 147 deletions

View file

@ -0,0 +1,118 @@
# Model-Optimized Weather Service
A variant-aware MCP server that exposes the same tools (`get_weather`, `get_forecast`) with different descriptions optimized for different LLM families. This is the primary use case described in SEP-2053.
**Pattern demonstrated:** Same tools, different descriptions per variant.
## Variants
| Variant | Description Style | Target |
|---|---|---|
| `claude-optimized` | Verbose, structured with explicit usage guidance | Anthropic Claude models |
| `gpt-optimized` | Concise, function-style with JSON Schema emphasis | OpenAI GPT models |
| `compact` | Minimal, bare-minimum descriptions | Context-constrained environments |
All three variants expose the same two tools with the same behavior — only the tool descriptions differ.
## Custom Ranking
Clients send a `"modelFamily"` hint (e.g., `"anthropic"`, `"openai"`). The ranking function matches the hint to the variant's `modelFamily`, falling back to priority order.
## Run
```bash
go run ./examples/server/model-optimized
```
The server listens on `http://localhost:8080`.
Connect any MCP client to `http://localhost:8080`.
## Try It
### 1. Initialize without hints (default ranking)
```bash
curl -s -X POST http://localhost:8080/mcp \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0", "id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"clientInfo": {"name": "test", "version": "1.0"},
"capabilities": {}
}
}'
```
The first variant in the response is the **default** — clients that don't specify a variant in subsequent requests will use it. Without hints, `claude-optimized` is the default:
```json
{
"availableVariants": [
{
"id": "claude-optimized",
"hints": { "contextSize": "verbose", "modelFamily": "anthropic" },
"status": "stable"
},
{
"id": "gpt-optimized",
"hints": { "contextSize": "standard", "modelFamily": "openai" },
"status": "stable"
},
{
"id": "compact",
"hints": { "contextSize": "compact", "modelFamily": "any" },
"status": "stable"
}
]
}
```
### 2. Initialize with an OpenAI hint (ranking changes)
```bash
curl -s -X POST http://localhost:8080/mcp \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0", "id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"clientInfo": {"name": "test", "version": "1.0"},
"capabilities": {
"experimental": {
"io.modelcontextprotocol/server-variants": {
"variantHints": {
"hints": {"modelFamily": "openai"}
}
}
}
}
}
}'
```
Now `gpt-optimized` is ranked first and becomes the default:
```json
{
"availableVariants": [
{
"id": "gpt-optimized",
"hints": { "contextSize": "standard", "modelFamily": "openai" },
"status": "stable"
},
{
"id": "claude-optimized",
"hints": { "contextSize": "verbose", "modelFamily": "anthropic" },
"status": "stable"
},
{
"id": "compact",
"hints": { "contextSize": "compact", "modelFamily": "any" },
"status": "stable"
}
]
}
```

View file

@ -0,0 +1,116 @@
// Example: Model-Optimized Weather Service — the same tools with different
// descriptions and schemas optimized for different LLM families. This
// demonstrates the SEP-2053 primary use case where tool descriptions are
// tailored per model family for optimal tool-calling performance.
//
// Capability demonstrated: Same tools, different descriptions per variant.
//
// Variants:
// - claude-optimized: Detailed, structured descriptions with explicit guidance
// - gpt-optimized: Concise function-style descriptions with JSON Schema emphasis
// - compact: Minimal descriptions for context-constrained environments
//
// Run:
//
// go run ./examples/server/model-optimized
//
// Then connect any MCP client to http://localhost:8080.
package main
import (
"context"
"log"
"net/http"
"slices"
"strings"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/modelcontextprotocol/experimental-ext-variants/go/sdk/variants"
)
func main() {
// Claude-optimized variant: verbose, structured descriptions with
// explicit guidance on when and how to use each tool.
claudeServer := mcp.NewServer(&mcp.Implementation{Name: "weather-service", Version: "v1.0.0"}, nil)
mcp.AddTool(claudeServer, &mcp.Tool{
Name: "get_weather",
Description: "Get the current weather conditions for a specific location. " +
"Use this tool when the user asks about current weather, temperature, or conditions. " +
"Accepts city names (e.g., 'San Francisco, CA'), country names, or lat/long coordinates. " +
"Returns temperature, humidity, wind speed, and a human-readable condition description. " +
"Prefer this over get_forecast when the user only needs current conditions.",
}, getWeather)
mcp.AddTool(claudeServer, &mcp.Tool{
Name: "get_forecast",
Description: "Get a multi-day weather forecast for a specific location. " +
"Use this tool when the user asks about future weather, upcoming conditions, or wants to plan ahead. " +
"Returns daily high/low temperatures and conditions for 1-7 days. " +
"Defaults to 3 days if not specified. Use get_weather instead if only current conditions are needed.",
}, getForecast)
// GPT-optimized variant: concise function-style descriptions with
// JSON Schema emphasis for OpenAI-family models.
gptServer := mcp.NewServer(&mcp.Implementation{Name: "weather-service", Version: "v1.0.0"}, nil)
mcp.AddTool(gptServer, &mcp.Tool{
Name: "get_weather",
Description: "Returns current weather data (temperature, humidity, wind, conditions) for a given location.",
}, getWeather)
mcp.AddTool(gptServer, &mcp.Tool{
Name: "get_forecast",
Description: "Returns multi-day forecast (high/low temps, conditions) for a location. Days: 1-7, default 3.",
}, getForecast)
// Compact variant: minimal descriptions for context-constrained
// environments where token budget is limited.
compactServer := mcp.NewServer(&mcp.Implementation{Name: "weather-service", Version: "v1.0.0"}, nil)
mcp.AddTool(compactServer, &mcp.Tool{
Name: "get_weather",
Description: "Current weather for a location.",
}, getWeather)
mcp.AddTool(compactServer, &mcp.Tool{
Name: "get_forecast",
Description: "Multi-day forecast for a location.",
}, getForecast)
vs := variants.NewServer(&mcp.Implementation{Name: "weather-service", Version: "v1.0.0"}).
WithVariant(variants.ServerVariant{
ID: "claude-optimized",
Description: "Detailed, structured tool descriptions with explicit usage guidance. Optimized for Anthropic Claude models that benefit from rich context and clear instructions.",
Hints: map[string]string{"modelFamily": "anthropic", "contextSize": "verbose"},
Status: variants.Stable,
}, claudeServer, 0).
WithVariant(variants.ServerVariant{
ID: "gpt-optimized",
Description: "Concise function-style descriptions with JSON Schema emphasis. Optimized for OpenAI GPT models that work well with terse, specification-like tool definitions.",
Hints: map[string]string{"modelFamily": "openai", "contextSize": "standard"},
Status: variants.Stable,
}, gptServer, 1).
WithVariant(variants.ServerVariant{
ID: "compact",
Description: "Minimal tool descriptions for context-constrained environments. Use when token budget is severely limited or tools are self-explanatory from their schemas.",
Hints: map[string]string{"modelFamily": "any", "contextSize": "compact"},
Status: variants.Stable,
}, compactServer, 2).
// Custom ranking: match by modelFamily hint, fall back to priority.
WithRanking(func(_ context.Context, hints variants.VariantHints, vs []variants.ServerVariant) []variants.ServerVariant {
requested, _ := variants.HintValue[string](hints, "modelFamily")
slices.SortStableFunc(vs, func(a, b variants.ServerVariant) int {
aMatch := strings.EqualFold(a.Hints["modelFamily"], requested)
bMatch := strings.EqualFold(b.Hints["modelFamily"], requested)
if aMatch != bMatch {
if aMatch {
return -1
}
return 1
}
return a.Priority() - b.Priority()
})
return vs
})
handler := variants.NewStreamableHTTPHandler(vs, nil)
log.Println("Listening on :8080")
log.Fatal(http.ListenAndServe(":8080", handler))
}

View file

@ -0,0 +1,91 @@
package main
import (
"context"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
type GetWeatherInput struct {
Location string `json:"location" jsonschema:"location name or coordinates (e.g. 'San Francisco, CA' or '37.7749,-122.4194')"`
Units string `json:"units,omitempty" jsonschema:"temperature units: celsius, fahrenheit (default: celsius)"`
}
type GetWeatherOutput struct {
Location string `json:"location"`
Temperature float64 `json:"temperature"`
Units string `json:"units"`
Condition string `json:"condition"`
Humidity int `json:"humidity"`
WindSpeed float64 `json:"windSpeed"`
}
type GetForecastInput struct {
Location string `json:"location" jsonschema:"location name or coordinates"`
Days int `json:"days,omitempty" jsonschema:"number of days to forecast (1-7, default: 3)"`
Units string `json:"units,omitempty" jsonschema:"temperature units: celsius, fahrenheit (default: celsius)"`
}
type ForecastDay struct {
Date string `json:"date"`
High float64 `json:"high"`
Low float64 `json:"low"`
Condition string `json:"condition"`
}
type GetForecastOutput struct {
Location string `json:"location"`
Units string `json:"units"`
Forecast []ForecastDay `json:"forecast"`
}
func getWeather(_ context.Context, _ *mcp.CallToolRequest, in GetWeatherInput) (*mcp.CallToolResult, GetWeatherOutput, error) {
units := in.Units
if units == "" {
units = "celsius"
}
temp := 22.5
if units == "fahrenheit" {
temp = 72.5
}
return nil, GetWeatherOutput{
Location: in.Location,
Temperature: temp,
Units: units,
Condition: "partly cloudy",
Humidity: 65,
WindSpeed: 12.3,
}, nil
}
func getForecast(_ context.Context, _ *mcp.CallToolRequest, in GetForecastInput) (*mcp.CallToolResult, GetForecastOutput, error) {
units := in.Units
if units == "" {
units = "celsius"
}
days := in.Days
if days == 0 {
days = 3
}
forecast := make([]ForecastDay, days)
conditions := []string{"sunny", "partly cloudy", "cloudy", "light rain", "clear", "overcast", "scattered showers"}
for i := range forecast {
high := 24.0 + float64(i)
low := 14.0 + float64(i)
if units == "fahrenheit" {
high = 75.0 + float64(i)*2
low = 57.0 + float64(i)*2
}
forecast[i] = ForecastDay{
Date: "2025-01-0" + string(rune('1'+i)),
High: high,
Low: low,
Condition: conditions[i%len(conditions)],
}
}
return nil, GetForecastOutput{
Location: in.Location,
Units: units,
Forecast: forecast,
}, nil
}