Define types and architecture for a variant-aware MCP server (#2)

Defines the core types and API for server variants extension, laying
groundwork for multi-variant server implementation. Server.Run() is not
  yet implemented but architecture is designed and documented.

  - Add Server type with variant registration 
  - Implement default priority-based ranking 
  - Define ServerVariant, VariantHints, and related types per SEP spec
  - Add comprehensive example demonstrating intended multi-variant usage
  - Document implementation plan for Server.Run() dispatch architecture
  
  
  Next PRs:
  
  -  Unit Tests
  -  Implementation for Server.Run
  -  Conformance Testing

Signed-off-by: Kurt Degiorgio <kdegiorgio@bloomberg.net>
This commit is contained in:
Kurt Degiorgio 2026-02-11 23:36:11 +00:00 • committed by GitHub
commit dbf99bebf0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 576 additions and 7 deletions

View file

@ -0,0 +1,111 @@
// Example: Setting up a variant-aware MCP server with multiple variants.
package main
import (
"context"
"log"
mcp "github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/modelcontextprotocol/experimental-ext-variants/go/sdk/variants"
)
// -- Coding variant tool types ------------------------------------------------
type AnalyzeCodeInput struct {
Code string `json:"code" jsonschema:"source code to analyze"`
Language string `json:"language" jsonschema:"programming language"`
}
type AnalyzeCodeOutput struct {
Issues []string `json:"issues"`
Suggestions []string `json:"suggestions"`
}
func AnalyzeCode(_ context.Context, _ *mcp.CallToolRequest, in AnalyzeCodeInput) (*mcp.CallToolResult, AnalyzeCodeOutput, error) {
return nil, AnalyzeCodeOutput{
Issues: []string{"unused variable on line 3"},
Suggestions: []string{"consider using a switch statement"},
}, nil
}
type RefactorInput struct {
Code string `json:"code" jsonschema:"source code to refactor"`
Action string `json:"action" jsonschema:"refactoring action, e.g. extract-function"`
}
type RefactorOutput struct {
Refactored string `json:"refactored"`
}
func Refactor(_ context.Context, _ *mcp.CallToolRequest, in RefactorInput) (*mcp.CallToolResult, RefactorOutput, error) {
return nil, RefactorOutput{Refactored: "// refactored\n" + in.Code}, nil
}
// -- Compact variant tool types -----------------------------------------------
type SummarizeInput struct {
Text string `json:"text" jsonschema:"text to summarize"`
}
type SummarizeOutput struct {
Summary string `json:"summary"`
}
func Summarize(_ context.Context, _ *mcp.CallToolRequest, in SummarizeInput) (*mcp.CallToolResult, SummarizeOutput, error) {
return nil, SummarizeOutput{Summary: in.Text[:min(len(in.Text), 50)]}, nil
}
type LookupInput struct {
Query string `json:"query" jsonschema:"lookup query"`
}
type LookupOutput struct {
Result string `json:"result"`
}
func Lookup(_ context.Context, _ *mcp.CallToolRequest, in LookupInput) (*mcp.CallToolResult, LookupOutput, error) {
return nil, LookupOutput{Result: "result for: " + in.Query}, nil
}
// -----------------------------------------------------------------------------
func main() {
// Server 1: coding-focused tools
server1 := mcp.NewServer(&mcp.Implementation{Name: "my-server", Version: "v1.0.0"}, nil)
mcp.AddTool(server1, &mcp.Tool{Name: "analyze_code", Description: "Perform static analysis on source code"}, AnalyzeCode)
mcp.AddTool(server1, &mcp.Tool{Name: "refactor", Description: "Apply a refactoring action to source code"}, Refactor)
// Server 2: compact / minimal tools
server2 := mcp.NewServer(&mcp.Implementation{Name: "my-server", Version: "v1.0.0"}, nil)
mcp.AddTool(server2, &mcp.Tool{Name: "summarize", Description: "Summarize text"}, Summarize)
mcp.AddTool(server2, &mcp.Tool{Name: "lookup", Description: "Quick fact lookup"}, Lookup)
coding := variants.ServerVariant{
ID: "coding-assistant",
Description: "Optimized for coding workflows",
Hints: map[string]string{"useCase": "coding", "contextSize": "standard"},
Status: variants.Stable,
}
compact := variants.ServerVariant{
ID: "compact",
Description: "Minimal token usage",
Hints: map[string]string{"contextSize": "small"},
Status: variants.Experimental,
}
vs := variants.NewServer().
WithVariant(coding, server1, 0).
WithVariant(compact, server2, 1).
WithRanking(func(_ context.Context, _ variants.VariantHints, vs []variants.ServerVariant) []variants.ServerVariant {
// rank based on client hints, return sorted
// note: this is optional, defults to ranking by priority.
return vs
})
ctx := context.Background()
if err := vs.Run(ctx, nil); err != nil {
log.Fatal(err)
}
}

View file

@ -1,3 +1,13 @@
module github.com/modelcontextprotocol/experimental-ext-variants/go/sdk
go 1.23.0
go 1.23.0
toolchain go1.24.3
require github.com/modelcontextprotocol/go-sdk v1.2.0
require (
github.com/google/jsonschema-go v0.3.0 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
)

14
go/sdk/go.sum Normal file
View file

@ -0,0 +1,14 @@
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s=
github.com/modelcontextprotocol/go-sdk v1.2.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
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=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=

View file

@ -0,0 +1,39 @@
// Copyright 2025 The MCP Variants Authors. All rights reserved.
// Use of this source code is governed by a Apache-2.0
// license that can be found in the LICENSE file.
package variants
import (
"context"
"slices"
)
// defaultRankingFunc is the built-in ranking function used when no custom
// RankingFunc is provided. It sorts variants by priority (lowest first),
// using stable-before-experimental-before-deprecated as a tiebreaker.
func defaultRankingFunc(_ context.Context, _ VariantHints, vs []ServerVariant) []ServerVariant {
ranked := slices.Clone(vs)
slices.SortStableFunc(ranked, func(a, b ServerVariant) int {
if a.Priority() != b.Priority() {
return a.Priority() - b.Priority()
}
return statusWeight(a.Status) - statusWeight(b.Status)
})
return ranked
}
// statusWeight returns a sort weight for a VariantStatus.
// Lower is better: stable < experimental < deprecated.
func statusWeight(s VariantStatus) int {
switch s {
case Stable, "":
return 0
case Experimental:
return 1
case Deprecated:
return 2
default:
return 3
}
}

223
go/sdk/variants/server.go Normal file
View file

@ -0,0 +1,223 @@
// Copyright 2025 The MCP Variants Authors. All rights reserved.
// Use of this source code is governed by a Apache-2.0
// license that can be found in the LICENSE file.
package variants
import (
"context"
"errors"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// variantEntry binds a ServerVariant to its backing mcp.Server.
type variantEntry struct {
variant ServerVariant
server *mcp.Server
}
// Server is a variant-aware MCP server that multiplexes across multiple
// mcp.Server instances, each associated with a ServerVariant. During
// initialization, clients provide VariantHints and the server responds with a
// ranked list of available variants. Per-request variant selection is carried
// in the _meta field.
type Server struct {
variants []variantEntry
variantMap map[string]*mcp.Server // ID -> server for O(1) lookup
rankingFunc RankingFunc
}
// NewServer creates a new variant-aware server with no registered variants.
func NewServer() *Server {
return &Server{
variantMap: make(map[string]*mcp.Server),
}
}
// WithVariant registers a ServerVariant backed by the given mcp.Server.
// priority determines the default ordering when no RankingFunc is set;
// lower values indicate higher importance (0 = highest priority). By
// default, the variant with the lowest priority value will appear first
// in the list and serve as the recommended default for clients. This
// behavior can be overridden by providing a custom RankingFunc.
//
// Per the SEP, the first variant in the ranked list MUST have status
// "stable" (unless the client explicitly requested experimental variants
// via variantHints). Priority ordering is respected within that
// constraint: variants are sorted by priority, but the first stable
// variant is always promoted to the front of the list.
//
// Variant IDs must be unique; registering a duplicate panics.
func (s *Server) WithVariant(v ServerVariant, mcpServer *mcp.Server, priority int) *Server {
if _, exists := s.variantMap[v.ID]; exists {
panic("variants: duplicate variant ID: " + v.ID)
}
v.priority = priority
s.variants = append(s.variants, variantEntry{variant: v, server: mcpServer})
s.variantMap[v.ID] = mcpServer
return s
}
// WithRanking sets a custom ranking function used to order variants based
// on client hints during initialization. The function should return variants
// sorted by relevance, with the most appropriate variant first. If nil,
// variants are ordered by their priority value (lowest first).
//
// The ranking function does not need to enforce the SEP's stable-first
// invariant. After the function returns, the SDK will automatically promote
// the first stable variant to the front of the list (per the SEP: "The first
// element MUST have status stable unless the client explicitly requested
// experimental variants via variantHints"). The rest of the ranking order
// is preserved.
//
// Returns the receiver for chaining.
func (s *Server) WithRanking(fn RankingFunc) *Server {
s.rankingFunc = fn
return s
}
// Variants returns a copy of all registered ServerVariant values in
// registration order.
func (s *Server) Variants() []ServerVariant {
out := make([]ServerVariant, len(s.variants))
for i, e := range s.variants {
out[i] = e.variant
}
return out
}
// RankedVariants returns the registered variants ranked according to the
// configured RankingFunc (or the default priority-based ranking if none is
// set).
func (s *Server) RankedVariants(ctx context.Context, hints VariantHints) []ServerVariant {
all := s.Variants()
if len(all) == 0 {
return all
}
rankFn := s.rankingFunc
if rankFn == nil {
rankFn = defaultRankingFunc
}
return rankFn(ctx, hints, all)
}
// ServerFor returns the mcp.Server associated with the given variant ID, or
// nil if no variant with that ID is registered.
func (s *Server) ServerFor(id string) *mcp.Server {
return s.variantMap[id]
}
// Run starts the variant server.
func (s *Server) Run(ctx context.Context, t mcp.Transport) error {
// TODO: Implement Server.Run using a method-dispatching proxy pattern.
//
// Architecture: the variant Server acts as a "front" MCP server that owns
// the real transport, handles lifecycle/discovery methods itself, and
// dispatches tool/resource/prompt calls to the correct inner *mcp.Server
// based on _meta["io.modelcontextprotocol/server-variant"].
//
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
//
// Extension ID (plural, for capability negotiation):
// "io.modelcontextprotocol/server-variants"
//
// Per-request _meta key (singular, for variant selection):
// "io.modelcontextprotocol/server-variant"
//
// ---------------------------------------------------------------------------
// Assumptions (v1)
// ---------------------------------------------------------------------------
//
// We need to make sure the SEP's session stability invariant is satisfied:
//
// "The server MUST ensure that the same variant is recommended and selected for the entire session, unless the client explicitly requests otherwise via variantHints."
//
// For the first version we will relax this constraint to avoid maintaining state.
//
// ---------------------------------------------------------------------------
// Steps
// ---------------------------------------------------------------------------
//
// 1. Create a "front" *mcp.Server inside Run. Configure it with
// ServerOptions.Capabilities set to the union of all variants'
// capability flags. For example, if any variant has tools, set
// Capabilities.Tools = &ToolCapabilities{ListChanged: true}. This
// causes the SDK to advertise the capability during initialize
// without registering any actual tools, resources, or prompts.
// The inner *mcp.Server instances are never connected to a
// transport — they are only used as handler registries.
//
// 2. Add a receiving middleware via AddReceivingMiddleware on the
// front server. This single middleware handles all variant-aware
// dispatch:
//
// a. "initialize": read variantHints from the client's extension
// payload at capabilities.extensions[
// "io.modelcontextprotocol/server-variants"]. Call
// RankedVariants() and call next() to let the SDK produce
// the base InitializeResult, then inject availableVariants
// (including id, description, hints, status, and
// deprecationInfo where applicable) into the response at
// capabilities.extensions[
// "io.modelcontextprotocol/server-variants"]. Include
// moreVariantsAvailable if only a subset is returned.
//
// b. "tools/list", "resources/list", "prompts/list",
// "resources/templates/list", "completion/complete":
// read _meta["io.modelcontextprotocol/server-variant"] to
// determine the active variant (default to first-ranked if
// absent). Look up the corresponding inner *mcp.Server and
// forward the request to its handler. Return only that
// variant's items. Cursors returned in paginated responses
// MUST be variant-scoped — embed the variant ID so that
// cross-variant cursor reuse is detectable.
//
// c. "tools/call", "resources/read", "prompts/get":
// read _meta["io.modelcontextprotocol/server-variant"] from
// the request. Validate the variant ID:
// - If the ID is not in the session's availableVariants,
// return JSON-RPC error -32602 with message
// "Invalid server variant" and data containing
// { requestedVariant, availableVariants } (scoped to
// the authenticated principal's visible variants).
// - If absent, default to the first-ranked variant.
// Resolve the inner *mcp.Server via ServerFor(id) and
// forward the request.
//
// d. "resources/subscribe": read the variant from _meta,
// validate as in (c). Resource notifications for this subscription
// MUST carry _meta["io.modelcontextprotocol/server-variant"]
// matching the bound variant. If the resource disappears
// from the variant, send a resource_updated notification
// followed by list_changed.
//
// e. "notifications/tools/list_changed",
// "notifications/prompts/list_changed",
// "notifications/resources/list_changed": when an inner
// server emits a list_changed notification, the middleware
// re-emits it on the front server with
// _meta["io.modelcontextprotocol/server-variant"] set to
// the originating variant's ID.
//
// f. All other methods ("ping", etc.):
// pass through to the front server's default handler.
//
// 3. Call frontServer.Run(ctx, t) to own the transport and manage
// the session lifecycle. The front server handles the JSON-RPC
// connection, SSE streaming, progress notifications, and
// session teardown.
//
// ---------------------------------------------------------------------------
// Backward compatibility
// ---------------------------------------------------------------------------
//
// If the client does not send the extension payload during
// initialize, the server omits availableVariants from the response
// and uses the first-ranked variant for all requests. The server
// behaves identically to a non-variant-aware server.
//
return errors.New("variants: Run not implemented")
}

View file

@ -2,5 +2,13 @@
// Use of this source code is governed by a Apache-2.0
// license that can be found in the LICENSE file.
// Package variants implements MCP Variants for experimental extensions.
package variants
import "testing"
func TestPlaceholder(t *testing.T) {
// Placeholder test to ensure go test runs successfully
if false {
t.Error("This should not happen")
}
}

178
go/sdk/variants/types.go Normal file
View file

@ -0,0 +1,178 @@
// Copyright 2025 The MCP Variants Authors. All rights reserved.
// Use of this source code is governed by a Apache-2.0
// license that can be found in the LICENSE file.
package variants
import "context"
// ---------------------------------------------------------------------------
// Variant status
// ---------------------------------------------------------------------------
// VariantStatus represents the stability status of a server variant.
type VariantStatus string
const (
// Stable indicates a production-ready variant recommended for general use.
Stable VariantStatus = "stable"
// Experimental indicates a variant that may change without notice; use for testing.
Experimental VariantStatus = "experimental"
// Deprecated indicates a variant that will be removed in a future release.
Deprecated VariantStatus = "deprecated"
)
// ---------------------------------------------------------------------------
// Deprecation info
// ---------------------------------------------------------------------------
// DeprecationInfo provides migration guidance for deprecated variants.
type DeprecationInfo struct {
// Message is a human-readable message explaining why this variant is
// deprecated and how to migrate.
Message string `json:"message"`
// Replacement is the suggested replacement variant identifier.
Replacement string `json:"replacement,omitempty"`
// RemovalDate is an optional ISO 8601 date when this variant is planned
// to be removed. Servers SHOULD continue to support the variant until
// that date.
RemovalDate string `json:"removalDate,omitempty"`
}
// ---------------------------------------------------------------------------
// Server variant
// ---------------------------------------------------------------------------
// ServerVariant describes a server capability variant that clients can select.
// Each variant represents a distinct configuration of all server capabilities
// (tools, resources, prompts, subscriptions).
type ServerVariant struct {
// ID is a unique identifier for this variant. Freeform string that servers
// define. Examples: "claude-optimized", "gpt-optimized", "compact",
// "agent-plan".
//
// Each variant's ID MUST be unique within availableVariants.
ID string `json:"id"`
// Description is a human-readable description of this variant, suitable
// for display to users or for LLM reasoning about variant selection.
//
// SHOULD include:
// - Target use case or model family
// - Key characteristics or optimizations
// - Trade-offs compared to other variants
Description string `json:"description"`
// Hints are key-value pairs providing structured metadata for intelligent
// variant selection. Clients and LLMs can use these hints to
// programmatically filter and rank variants.
//
// Unknown hint keys MUST be ignored by clients and servers.
Hints map[string]string `json:"hints,omitempty"`
// priority determines the default ordering when no RankingFunc is set;
// lower values indicate higher importance (0 = highest priority).
// This field is set by Server.WithVariant and is readable via the
// Priority() getter for use in custom RankingFunc implementations.
priority int
// Status is the stability status of this variant.
// Defaults to Stable if empty.
Status VariantStatus `json:"status,omitempty"`
// DeprecationInfo provides migration guidance when Status is Deprecated.
DeprecationInfo *DeprecationInfo `json:"deprecationInfo,omitempty"`
}
// Priority returns the variant's priority value. Lower values indicate
// higher importance (0 = highest priority).
func (v ServerVariant) Priority() int {
return v.priority
}
// ---------------------------------------------------------------------------
// Hint keys
// ---------------------------------------------------------------------------
// HintKey is a well-known key for use in VariantHints.Hints and
// ServerVariant.Hints.
type HintKey = string
// Well-known hint keys defined by the SEP (Common Hint Vocabulary).
const (
// HintModelFamily identifies the target model family/provider.
// Common values: "anthropic", "openai", "google", "meta", "local", "any".
HintModelFamily HintKey = "modelFamily"
// HintUseCase identifies the intended usage scenario.
// Common values: "autonomous-agent", "human-assistant", "ide", "api",
// "chat", "planning", "execution".
HintUseCase HintKey = "useCase"
// HintContextSize indicates the desired verbosity / token efficiency.
// Common values: "compact", "standard", "verbose".
HintContextSize HintKey = "contextSize"
// HintRenderingCapabilities describes the client's rendering support.
// Common values: "rich", "markdown", "text-only".
HintRenderingCapabilities HintKey = "renderingCapabilities"
// HintLanguageOptimization indicates natural language optimization.
// Common values: "en", "multilingual", "code-focused".
HintLanguageOptimization HintKey = "languageOptimization"
)
// ---------------------------------------------------------------------------
// Variant hints
// ---------------------------------------------------------------------------
// VariantHints are structured hints provided by the client to help the server
// rank available variants.
type VariantHints struct {
// Description is a human-readable description of the client's context and
// requirements.
Description string `json:"description,omitempty"`
// Hints are key-value pairs providing structured metadata for variant
// selection. Values can be a single string or an array of strings (in
// order of preference).
//
// Unknown hint keys MUST be ignored by clients and servers.
Hints map[string]any `json:"hints,omitempty"`
}
// HintValue retrieves a typed value from the Hints map for the given key.
// It returns the zero value and false if the key is missing, the map is nil,
// or the stored value is not assignable to T.
func HintValue[T any](h VariantHints, key string) (T, bool) {
var zero T
if h.Hints == nil {
return zero, false
}
v, ok := h.Hints[key]
if !ok {
return zero, false
}
t, ok := v.(T)
return t, ok
}
// ---------------------------------------------------------------------------
// Ranking function
// ---------------------------------------------------------------------------
// RankingFunc is called during initialization to rank available variants based
// on the client's hints. It receives the client-provided hints and the full
// set of registered variants, and must return them sorted by relevance with
// the highest-priority variant first. The first variant in the returned slice
// is the recommended default and will be used when a client does not
// explicitly select a variant via _meta.
//
// Each ServerVariant carries its Priority field (set via WithVariant), which
// the ranking function may use as a baseline signal alongside client hints.
//
// Note: The default (first) variant is also used when the client does not
// support variants at all.
type RankingFunc func(ctx context.Context, hints VariantHints, variants []ServerVariant) []ServerVariant