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:
parent
4f98e0aab3
commit
dbf99bebf0
8 changed files with 576 additions and 7 deletions
111
go/sdk/examples/server/variants/main.go
Normal file
111
go/sdk/examples/server/variants/main.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
14
go/sdk/go.sum
Normal 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=
|
||||
39
go/sdk/variants/ranking.go
Normal file
39
go/sdk/variants/ranking.go
Normal 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
223
go/sdk/variants/server.go
Normal 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")
|
||||
}
|
||||
|
|
@ -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
178
go/sdk/variants/types.go
Normal 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
|
||||
Loading…
Reference in a new issue