Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions go/sdk/examples/server/variants/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
12 changes: 11 additions & 1 deletion go/sdk/go.mod
Original file line number Diff line number Diff line change
@@ -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 changes: 14 additions & 0 deletions go/sdk/go.sum
Original file line number Diff line number Diff line change
@@ -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 changes: 39 additions & 0 deletions go/sdk/variants/ranking.go
Original file line number Diff line number Diff line change
@@ -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
}
}
Loading
Loading