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,36 @@
# Trading Platform
A variant-aware MCP server demonstrating API versioning and lifecycle management. Uses all three variant statuses (`stable`, `experimental`, `deprecated`) and `DeprecationInfo` to guide clients through API migrations.
**Pattern demonstrated:** Variant lifecycle, deprecation info, API versioning.
## Variants
| Variant | Status | Tools | Description |
|---|---|---|---|
| `v2-stable` | Stable | `place_order`, `get_quotes`, `get_portfolio`, `cancel_order` | Production API |
| `v3-preview` | Experimental | `place_order_v3`, `stream_quotes`, `get_portfolio_v3` | Next-gen API with streaming |
| `v1-legacy` | Deprecated | `trade`, `quote`, `balance` | Legacy API, removal date 2026-06-30 |
| `analysis-only` | Stable | `get_quotes`, `get_portfolio`, `get_historical_data` | Read-only analytics subset |
## Deprecation Info
The `v1-legacy` variant includes `DeprecationInfo` with a replacement and scheduled removal date, demonstrating how servers communicate migration paths to clients:
```json
{
"deprecationInfo": {
"message": "v1 API is deprecated. Migrate to v2-stable for improved order types, multi-symbol quotes, and portfolio tracking.",
"replacement": "v2-stable",
"removalDate": "2026-06-30"
}
}
```
## Run
```bash
go run ./examples/server/trading
```
The server listens on `http://localhost:8080`.

View file

@ -0,0 +1,130 @@
// Example: Trading Platform — API versioning and lifecycle management using
// MCP server variants. Demonstrates DeprecationInfo, all three variant
// statuses (stable, experimental, deprecated), and access control patterns
// (read-only subset).
//
// Capability demonstrated: Variant lifecycle, DeprecationInfo, API migration.
//
// Variants:
// - v2-stable: Production API with order management and portfolio tools
// - v3-preview: Next-gen experimental API with streaming and enhanced orders
// - v1-legacy: Deprecated legacy API scheduled for removal
// - analysis-only: Read-only subset for analytics and monitoring
//
// Run:
//
// go run ./examples/server/trading
//
// Then connect any MCP client to http://localhost:8080.
package main
import (
"log"
"net/http"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/modelcontextprotocol/experimental-ext-variants/go/sdk/variants"
)
func main() {
// V2 stable: production API
v2Server := mcp.NewServer(&mcp.Implementation{Name: "trading-platform", Version: "v2.0.0"}, nil)
mcp.AddTool(v2Server, &mcp.Tool{
Name: "place_order",
Description: "Place a market or limit order for a given symbol",
}, placeOrder)
mcp.AddTool(v2Server, &mcp.Tool{
Name: "get_quotes",
Description: "Get real-time quotes for one or more symbols",
}, getQuotes)
mcp.AddTool(v2Server, &mcp.Tool{
Name: "get_portfolio",
Description: "Get portfolio positions and account balance",
}, getPortfolio)
mcp.AddTool(v2Server, &mcp.Tool{
Name: "cancel_order",
Description: "Cancel a pending order by order ID",
}, cancelOrder)
// V3 preview: next-gen experimental API
v3Server := mcp.NewServer(&mcp.Implementation{Name: "trading-platform", Version: "v3.0.0-preview"}, nil)
mcp.AddTool(v3Server, &mcp.Tool{
Name: "place_order_v3",
Description: "Place an order with advanced options: stop/stop-limit types, time-in-force, and custom tags",
}, placeOrderV3)
mcp.AddTool(v3Server, &mcp.Tool{
Name: "stream_quotes",
Description: "Subscribe to real-time quote streaming for one or more symbols",
}, streamQuotes)
mcp.AddTool(v3Server, &mcp.Tool{
Name: "get_portfolio_v3",
Description: "Get enhanced portfolio with margin data and optional greeks",
}, getPortfolioV3)
// V1 legacy: deprecated API scheduled for removal
v1Server := mcp.NewServer(&mcp.Implementation{Name: "trading-platform", Version: "v1.0.0"}, nil)
mcp.AddTool(v1Server, &mcp.Tool{
Name: "trade",
Description: "Execute a trade (deprecated — use place_order in v2-stable)",
}, trade)
mcp.AddTool(v1Server, &mcp.Tool{
Name: "quote",
Description: "Get a single stock quote (deprecated — use get_quotes in v2-stable)",
}, quote)
mcp.AddTool(v1Server, &mcp.Tool{
Name: "balance",
Description: "Get account balance (deprecated — use get_portfolio in v2-stable)",
}, balance)
// Analysis-only: read-only subset for analytics
analysisServer := mcp.NewServer(&mcp.Implementation{Name: "trading-platform", Version: "v2.0.0"}, nil)
mcp.AddTool(analysisServer, &mcp.Tool{
Name: "get_quotes",
Description: "Get real-time quotes for one or more symbols",
}, getQuotes)
mcp.AddTool(analysisServer, &mcp.Tool{
Name: "get_portfolio",
Description: "Get portfolio positions and account balance (read-only view)",
}, getPortfolio)
mcp.AddTool(analysisServer, &mcp.Tool{
Name: "get_historical_data",
Description: "Get historical OHLCV price data for a symbol over a given period",
}, getHistoricalData)
vs := variants.NewServer(&mcp.Implementation{Name: "trading-platform", Version: "v2.0.0"}).
WithVariant(variants.ServerVariant{
ID: "v2-stable",
Description: "Production trading API (v2). Full order management with market/limit orders, real-time quotes, portfolio tracking, and order cancellation.",
Hints: map[string]string{"com.example/apiGeneration": "v2", "contextSize": "standard"},
Status: variants.Stable,
}, v2Server, 0).
WithVariant(variants.ServerVariant{
ID: "v3-preview",
Description: "Next-generation trading API (v3 preview). Adds stop/stop-limit orders, streaming quotes, margin data, and custom order tags. May change without notice.",
Hints: map[string]string{"com.example/apiGeneration": "v3", "contextSize": "standard"},
Status: variants.Experimental,
}, v3Server, 1).
WithVariant(variants.ServerVariant{
ID: "v1-legacy",
Description: "Legacy trading API (v1). Provides basic trade, quote, and balance operations. Scheduled for removal — migrate to v2-stable.",
Hints: map[string]string{"com.example/apiGeneration": "v1", "contextSize": "compact"},
Status: variants.Deprecated,
DeprecationInfo: &variants.DeprecationInfo{
Message: "v1 API is deprecated. Migrate to v2-stable for improved order types, multi-symbol quotes, and portfolio tracking.",
Replacement: "v2-stable",
RemovalDate: "2026-06-30",
},
}, v1Server, 2).
WithVariant(variants.ServerVariant{
ID: "analysis-only",
Description: "Read-only analytics variant. Provides market data, portfolio viewing, and historical data without any order placement or modification capabilities.",
Hints: map[string]string{"com.example/apiGeneration": "v2", "useCase": "planning", "contextSize": "standard"},
Status: variants.Stable,
}, analysisServer, 1)
handler := variants.NewStreamableHTTPHandler(vs, nil)
log.Println("Listening on :8080")
log.Fatal(http.ListenAndServe(":8080", handler))
}

View file

@ -0,0 +1,296 @@
package main
import (
"context"
"fmt"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// -- V2 tools -----------------------------------------------------------------
type PlaceOrderInput struct {
Symbol string `json:"symbol" jsonschema:"ticker symbol (e.g. AAPL)"`
Side string `json:"side" jsonschema:"order side: buy, sell"`
Quantity int `json:"quantity" jsonschema:"number of shares"`
Type string `json:"type,omitempty" jsonschema:"order type: market, limit (default: market)"`
Price float64 `json:"price,omitempty" jsonschema:"limit price (required for limit orders)"`
}
type PlaceOrderOutput struct {
OrderID string `json:"orderId"`
Symbol string `json:"symbol"`
Side string `json:"side"`
Status string `json:"status"`
Price float64 `json:"filledPrice"`
}
func placeOrder(_ context.Context, _ *mcp.CallToolRequest, in PlaceOrderInput) (*mcp.CallToolResult, PlaceOrderOutput, error) {
price := in.Price
if price == 0 {
price = 185.50
}
return nil, PlaceOrderOutput{
OrderID: "ORD-20250115-001",
Symbol: in.Symbol,
Side: in.Side,
Status: "filled",
Price: price,
}, nil
}
type GetQuotesInput struct {
Symbols []string `json:"symbols" jsonschema:"list of ticker symbols"`
}
type Quote struct {
Symbol string `json:"symbol"`
Bid float64 `json:"bid"`
Ask float64 `json:"ask"`
Last float64 `json:"last"`
Volume int `json:"volume"`
}
type GetQuotesOutput struct {
Quotes []Quote `json:"quotes"`
}
func getQuotes(_ context.Context, _ *mcp.CallToolRequest, in GetQuotesInput) (*mcp.CallToolResult, GetQuotesOutput, error) {
quotes := make([]Quote, len(in.Symbols))
for i, sym := range in.Symbols {
quotes[i] = Quote{
Symbol: sym,
Bid: 185.40 + float64(i),
Ask: 185.60 + float64(i),
Last: 185.50 + float64(i),
Volume: 1500000 + i*100000,
}
}
return nil, GetQuotesOutput{Quotes: quotes}, nil
}
type GetPortfolioInput struct {
AccountID string `json:"accountId,omitempty" jsonschema:"account identifier (default: primary account)"`
}
type Position struct {
Symbol string `json:"symbol"`
Quantity int `json:"quantity"`
AvgCost float64 `json:"avgCost"`
Current float64 `json:"currentPrice"`
PnL float64 `json:"unrealizedPnL"`
}
type GetPortfolioOutput struct {
AccountID string `json:"accountId"`
Positions []Position `json:"positions"`
CashBal float64 `json:"cashBalance"`
TotalValue float64 `json:"totalValue"`
}
func getPortfolio(_ context.Context, _ *mcp.CallToolRequest, in GetPortfolioInput) (*mcp.CallToolResult, GetPortfolioOutput, error) {
return nil, GetPortfolioOutput{
AccountID: "ACCT-001",
Positions: []Position{
{Symbol: "AAPL", Quantity: 100, AvgCost: 175.00, Current: 185.50, PnL: 1050.00},
{Symbol: "GOOGL", Quantity: 50, AvgCost: 140.00, Current: 178.25, PnL: 1912.50},
},
CashBal: 25000.00,
TotalValue: 52337.50,
}, nil
}
type CancelOrderInput struct {
OrderID string `json:"orderId" jsonschema:"order identifier to cancel"`
}
type CancelOrderOutput struct {
OrderID string `json:"orderId"`
Status string `json:"status"`
}
func cancelOrder(_ context.Context, _ *mcp.CallToolRequest, in CancelOrderInput) (*mcp.CallToolResult, CancelOrderOutput, error) {
return nil, CancelOrderOutput{
OrderID: in.OrderID,
Status: "cancelled",
}, nil
}
// -- V3 tools -----------------------------------------------------------------
type PlaceOrderV3Input struct {
Symbol string `json:"symbol" jsonschema:"ticker symbol"`
Side string `json:"side" jsonschema:"order side: buy, sell, short"`
Quantity int `json:"quantity" jsonschema:"number of shares"`
Type string `json:"type,omitempty" jsonschema:"order type: market, limit, stop, stop_limit (default: market)"`
Price float64 `json:"price,omitempty" jsonschema:"limit price"`
StopPrice float64 `json:"stopPrice,omitempty" jsonschema:"stop trigger price"`
TimeInForce string `json:"timeInForce,omitempty" jsonschema:"GTC, DAY, IOC, FOK (default: DAY)"`
Tags map[string]string `json:"tags,omitempty" jsonschema:"custom order tags for tracking"`
}
type PlaceOrderV3Output struct {
OrderID string `json:"orderId"`
Symbol string `json:"symbol"`
Side string `json:"side"`
Status string `json:"status"`
Price float64 `json:"filledPrice"`
TimeInForce string `json:"timeInForce"`
}
func placeOrderV3(_ context.Context, _ *mcp.CallToolRequest, in PlaceOrderV3Input) (*mcp.CallToolResult, PlaceOrderV3Output, error) {
price := in.Price
if price == 0 {
price = 185.50
}
tif := in.TimeInForce
if tif == "" {
tif = "DAY"
}
return nil, PlaceOrderV3Output{
OrderID: "ORD-V3-20250115-001",
Symbol: in.Symbol,
Side: in.Side,
Status: "filled",
Price: price,
TimeInForce: tif,
}, nil
}
type StreamQuotesInput struct {
Symbols []string `json:"symbols" jsonschema:"list of ticker symbols to stream"`
Interval int `json:"interval,omitempty" jsonschema:"update interval in milliseconds (default: 1000)"`
}
type StreamQuotesOutput struct {
Snapshot []Quote `json:"snapshot"`
StreamID string `json:"streamId"`
}
func streamQuotes(_ context.Context, _ *mcp.CallToolRequest, in StreamQuotesInput) (*mcp.CallToolResult, StreamQuotesOutput, error) {
quotes := make([]Quote, len(in.Symbols))
for i, sym := range in.Symbols {
quotes[i] = Quote{
Symbol: sym,
Bid: 185.40 + float64(i),
Ask: 185.60 + float64(i),
Last: 185.50 + float64(i),
Volume: 1500000 + i*100000,
}
}
return nil, StreamQuotesOutput{
Snapshot: quotes,
StreamID: "stream-001",
}, nil
}
type GetPortfolioV3Input struct {
AccountID string `json:"accountId,omitempty" jsonschema:"account identifier (default: primary account)"`
IncludeGreek bool `json:"includeGreeks,omitempty" jsonschema:"include options greeks in positions"`
}
type GetPortfolioV3Output struct {
AccountID string `json:"accountId"`
Positions []Position `json:"positions"`
CashBal float64 `json:"cashBalance"`
TotalValue float64 `json:"totalValue"`
MarginUsed float64 `json:"marginUsed"`
}
func getPortfolioV3(_ context.Context, _ *mcp.CallToolRequest, in GetPortfolioV3Input) (*mcp.CallToolResult, GetPortfolioV3Output, error) {
return nil, GetPortfolioV3Output{
AccountID: "ACCT-001",
Positions: []Position{
{Symbol: "AAPL", Quantity: 100, AvgCost: 175.00, Current: 185.50, PnL: 1050.00},
{Symbol: "GOOGL", Quantity: 50, AvgCost: 140.00, Current: 178.25, PnL: 1912.50},
},
CashBal: 25000.00,
TotalValue: 52337.50,
MarginUsed: 5000.00,
}, nil
}
// -- V1 legacy tools ----------------------------------------------------------
type TradeInput struct {
Ticker string `json:"ticker" jsonschema:"stock ticker"`
Action string `json:"action" jsonschema:"buy or sell"`
Shares int `json:"shares" jsonschema:"number of shares"`
}
type TradeOutput struct {
TradeID string `json:"tradeId"`
Status string `json:"status"`
}
func trade(_ context.Context, _ *mcp.CallToolRequest, in TradeInput) (*mcp.CallToolResult, TradeOutput, error) {
return nil, TradeOutput{
TradeID: fmt.Sprintf("T-%s-%d", in.Ticker, in.Shares),
Status: "executed",
}, nil
}
type QuoteInput struct {
Ticker string `json:"ticker" jsonschema:"stock ticker"`
}
type QuoteOutput struct {
Ticker string `json:"ticker"`
Price float64 `json:"price"`
}
func quote(_ context.Context, _ *mcp.CallToolRequest, in QuoteInput) (*mcp.CallToolResult, QuoteOutput, error) {
return nil, QuoteOutput{
Ticker: in.Ticker,
Price: 185.50,
}, nil
}
type BalanceInput struct {
Account string `json:"account,omitempty" jsonschema:"account name"`
}
type BalanceOutput struct {
Cash float64 `json:"cash"`
Total float64 `json:"total"`
}
func balance(_ context.Context, _ *mcp.CallToolRequest, in BalanceInput) (*mcp.CallToolResult, BalanceOutput, error) {
return nil, BalanceOutput{
Cash: 25000.00,
Total: 52337.50,
}, nil
}
// -- Analysis tools -----------------------------------------------------------
type GetHistoricalDataInput struct {
Symbol string `json:"symbol" jsonschema:"ticker symbol"`
Period string `json:"period,omitempty" jsonschema:"time period: 1d, 5d, 1mo, 3mo, 1y (default: 1mo)"`
Interval string `json:"interval,omitempty" jsonschema:"data interval: 1m, 5m, 1h, 1d (default: 1d)"`
}
type DataPoint struct {
Date string `json:"date"`
Open float64 `json:"open"`
High float64 `json:"high"`
Low float64 `json:"low"`
Close float64 `json:"close"`
Volume int `json:"volume"`
}
type GetHistoricalDataOutput struct {
Symbol string `json:"symbol"`
Data []DataPoint `json:"data"`
}
func getHistoricalData(_ context.Context, _ *mcp.CallToolRequest, in GetHistoricalDataInput) (*mcp.CallToolResult, GetHistoricalDataOutput, error) {
return nil, GetHistoricalDataOutput{
Symbol: in.Symbol,
Data: []DataPoint{
{Date: "2025-01-13", Open: 183.00, High: 185.00, Low: 182.50, Close: 184.75, Volume: 1200000},
{Date: "2025-01-14", Open: 184.75, High: 186.50, Low: 184.00, Close: 185.50, Volume: 1350000},
{Date: "2025-01-15", Open: 185.50, High: 187.25, Low: 185.00, Close: 186.00, Volume: 1100000},
},
}, nil
}