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
16 changed files with 1218 additions and 147 deletions

View file

@ -13,6 +13,34 @@ This repository provides a multi-language reference implementation of the varian
| TypeScript | `typescript/sdk/` | `@ext-modelcontextprotocol/variants` | Planned | | TypeScript | `typescript/sdk/` | `@ext-modelcontextprotocol/variants` | Planned |
## Examples (Go)
The Go implementation includes runnable example servers under [`go/sdk/examples/server/`](go/sdk/examples/server/):
### [`model-optimized/`](go/sdk/examples/server/model-optimized/)
Same tools, different descriptions per LLM family.
https://github.com/user-attachments/assets/5bb60cd2-291f-4940-abfe-f53852d35470
### [`github/`](go/sdk/examples/server/github/)
Different tool sets per variant with custom ranking.
https://github.com/user-attachments/assets/d770ac55-e988-4d89-bdf8-bcb3cf1e2e53
### [`research/`](go/sdk/examples/server/research/)
Context budget management via description verbosity.
### [`trading/`](go/sdk/examples/server/trading/)
API versioning, lifecycle statuses, and deprecation info.
### [`variants-stdio/`](go/sdk/examples/server/variants-stdio/)
Minimal single-variant setup.
## CI/CD ## CI/CD
This monorepo uses **path-based CI workflows** to efficiently test only what changes: This monorepo uses **path-based CI workflows** to efficiently test only what changes:

View file

@ -138,10 +138,7 @@ In **stateful mode** (default, stdio and HTTP), per-session inner connections ar
## Examples ## Examples
See [`examples/server/`](examples/server/) for runnable examples: See [`examples/server/`](examples/server/) for runnable examples.
- [`variants/`](examples/server/variants/) — stdio transport (single client)
- [`variants-http/`](examples/server/variants-http/) — HTTP transport (multiple concurrent clients)
## API ## API

View file

@ -0,0 +1,30 @@
# GitHub Developer Platform
A variant-aware MCP server that exposes different tool sets for different agent types, mirroring the GitHub MCP Server pattern described in SEP-2053's Prior Art section.
**Pattern demonstrated:** Different tool sets per variant with custom ranking.
## Variants
| Variant | Tools | Description |
|---|---|---|
| `code-review` | `list_pull_requests`, `get_diff`, `add_review_comment` | PR operations and code review |
| `project-management` | `list_issues`, `create_issue`, `add_label` | Issue tracking and labels |
| `security-readonly` | `list_security_alerts`, `get_advisory` | Security scanning (read-only) |
| `ci-automation` | `list_workflow_runs`, `trigger_workflow` | CI/CD workflow management |
## Custom Ranking
Clients send a `"domain"` hint during initialization. The ranking function boosts variants whose `domain` hint matches the client's requested domain, falling back to priority order.
## Run
```bash
go run ./examples/server/github
```
Connect any MCP client to `http://localhost:8080`.
## Demo
See [mcp-inspector-variants-demo.mp4](mcp-inspector-variants-demo.mp4) for a walkthrough using MCP Inspector.

View file

@ -1,5 +1,10 @@
// Example: A developer productivity platform (à la GitHub) that exposes // Example: GitHub MCP Server — a developer productivity platform that exposes
// different tool sets for different agent types using MCP server variants. // different tool sets for different agent types using MCP server variants.
// This mirrors the GitHub MCP Server pattern described in SEP-2053's Prior Art
// section, where a single server exposes code review, project management,
// security, and CI/CD tools as separate variants with custom ranking.
//
// Capability demonstrated: Different tool sets per variant, custom ranking.
// //
// Variants: // Variants:
// - code-review: PR operations, diffs, and review comments // - code-review: PR operations, diffs, and review comments
@ -7,18 +12,22 @@
// - security-readonly: security scanning alerts and advisories (read-only) // - security-readonly: security scanning alerts and advisories (read-only)
// - ci-automation: CI/CD workflow management and dispatch // - ci-automation: CI/CD workflow management and dispatch
// //
// Run over stdio: // Run:
// //
// go run . // go run ./examples/server/github
//
// Then connect any MCP client to http://localhost:8080.
package main package main
import ( import (
"context" "context"
"log" "log"
"net/http"
"slices"
"strings"
"github.com/modelcontextprotocol/go-sdk/mcp" "github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/modelcontextprotocol/experimental-ext-variants/go/sdk/examples/server/exampletools"
"github.com/modelcontextprotocol/experimental-ext-variants/go/sdk/variants" "github.com/modelcontextprotocol/experimental-ext-variants/go/sdk/variants"
) )
@ -28,52 +37,52 @@ func main() {
mcp.AddTool(codeReviewServer, &mcp.Tool{ mcp.AddTool(codeReviewServer, &mcp.Tool{
Name: "list_pull_requests", Name: "list_pull_requests",
Description: "List open pull requests, optionally filtered by author", Description: "List open pull requests, optionally filtered by author",
}, exampletools.ListPullRequests) }, listPullRequests)
mcp.AddTool(codeReviewServer, &mcp.Tool{ mcp.AddTool(codeReviewServer, &mcp.Tool{
Name: "get_diff", Name: "get_diff",
Description: "Get the diff for a pull request, including changed files and line counts", Description: "Get the diff for a pull request, including changed files and line counts",
}, exampletools.GetDiff) }, getDiff)
mcp.AddTool(codeReviewServer, &mcp.Tool{ mcp.AddTool(codeReviewServer, &mcp.Tool{
Name: "add_review_comment", Name: "add_review_comment",
Description: "Post a review comment on a specific line of a pull request", Description: "Post a review comment on a specific line of a pull request",
}, exampletools.AddReviewComment) }, addReviewComment)
// Project management variant: issue-focused tools for PM agents // Project management variant: issue-focused tools for PM agents
pmServer := mcp.NewServer(&mcp.Implementation{Name: "devplatform", Version: "v1.0.0"}, nil) pmServer := mcp.NewServer(&mcp.Implementation{Name: "devplatform", Version: "v1.0.0"}, nil)
mcp.AddTool(pmServer, &mcp.Tool{ mcp.AddTool(pmServer, &mcp.Tool{
Name: "list_issues", Name: "list_issues",
Description: "List issues, optionally filtered by state and labels", Description: "List issues, optionally filtered by state and labels",
}, exampletools.ListIssues) }, listIssues)
mcp.AddTool(pmServer, &mcp.Tool{ mcp.AddTool(pmServer, &mcp.Tool{
Name: "create_issue", Name: "create_issue",
Description: "Create a new issue with title, body, and optional labels", Description: "Create a new issue with title, body, and optional labels",
}, exampletools.CreateIssue) }, createIssue)
mcp.AddTool(pmServer, &mcp.Tool{ mcp.AddTool(pmServer, &mcp.Tool{
Name: "add_label", Name: "add_label",
Description: "Add labels to an existing issue", Description: "Add labels to an existing issue",
}, exampletools.AddLabel) }, addLabel)
// Security variant: read-only security scanning tools // Security variant: read-only security scanning tools
securityServer := mcp.NewServer(&mcp.Implementation{Name: "devplatform", Version: "v1.0.0"}, nil) securityServer := mcp.NewServer(&mcp.Implementation{Name: "devplatform", Version: "v1.0.0"}, nil)
mcp.AddTool(securityServer, &mcp.Tool{ mcp.AddTool(securityServer, &mcp.Tool{
Name: "list_security_alerts", Name: "list_security_alerts",
Description: "List code scanning alerts for a repository", Description: "List code scanning alerts for a repository",
}, exampletools.ListSecurityAlerts) }, listSecurityAlerts)
mcp.AddTool(securityServer, &mcp.Tool{ mcp.AddTool(securityServer, &mcp.Tool{
Name: "get_advisory", Name: "get_advisory",
Description: "Get details of a security advisory", Description: "Get details of a security advisory",
}, exampletools.GetAdvisory) }, getAdvisory)
// CI/CD variant: workflow management tools for automation agents // CI/CD variant: workflow management tools for automation agents
ciServer := mcp.NewServer(&mcp.Implementation{Name: "devplatform", Version: "v1.0.0"}, nil) ciServer := mcp.NewServer(&mcp.Implementation{Name: "devplatform", Version: "v1.0.0"}, nil)
mcp.AddTool(ciServer, &mcp.Tool{ mcp.AddTool(ciServer, &mcp.Tool{
Name: "list_workflow_runs", Name: "list_workflow_runs",
Description: "List recent workflow runs for a repository", Description: "List recent workflow runs for a repository",
}, exampletools.ListWorkflowRuns) }, listWorkflowRuns)
mcp.AddTool(ciServer, &mcp.Tool{ mcp.AddTool(ciServer, &mcp.Tool{
Name: "trigger_workflow", Name: "trigger_workflow",
Description: "Trigger a workflow dispatch event", Description: "Trigger a workflow dispatch event",
}, exampletools.TriggerWorkflow) }, triggerWorkflow)
vs := variants.NewServer(&mcp.Implementation{Name: "devplatform", Version: "v1.0.0"}). vs := variants.NewServer(&mcp.Implementation{Name: "devplatform", Version: "v1.0.0"}).
WithVariant(variants.ServerVariant{ WithVariant(variants.ServerVariant{
@ -99,10 +108,26 @@ func main() {
Description: "CI/CD workflow management. Trigger runs, monitor jobs, manage deployments designed for automation agents.", Description: "CI/CD workflow management. Trigger runs, monitor jobs, manage deployments designed for automation agents.",
Hints: map[string]string{"domain": "ci-cd", "accessLevel": "automation"}, Hints: map[string]string{"domain": "ci-cd", "accessLevel": "automation"},
Status: variants.Stable, Status: variants.Stable,
}, ciServer, 3) }, ciServer, 3).
// Custom ranking: boost variants whose "domain" hint matches the client's.
WithRanking(func(_ context.Context, hints variants.VariantHints, vs []variants.ServerVariant) []variants.ServerVariant {
requested, _ := variants.HintValue[string](hints, "domain")
slices.SortStableFunc(vs, func(a, b variants.ServerVariant) int {
aMatch := strings.Contains(strings.ToLower(a.Hints["domain"]), strings.ToLower(requested))
bMatch := strings.Contains(strings.ToLower(b.Hints["domain"]), strings.ToLower(requested))
if aMatch != bMatch {
if aMatch {
return -1
}
return 1
}
return a.Priority() - b.Priority()
})
return vs
})
ctx := context.Background() handler := variants.NewStreamableHTTPHandler(vs, nil)
if err := vs.Run(ctx, &mcp.StdioTransport{}); err != nil {
log.Fatal(err) log.Println("Listening on :8080")
} log.Fatal(http.ListenAndServe(":8080", handler))
} }

View file

@ -0,0 +1,246 @@
package main
import (
"context"
"fmt"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// -- Code Review variant tools ------------------------------------------------
type ListPullRequestsInput struct {
Repo string `json:"repo" jsonschema:"repository in owner/name format"`
State string `json:"state,omitempty" jsonschema:"filter by state: open, closed, all (default: open)"`
Author string `json:"author,omitempty" jsonschema:"filter by PR author username"`
}
type PullRequest struct {
Number int `json:"number"`
Title string `json:"title"`
Author string `json:"author"`
State string `json:"state"`
}
type ListPullRequestsOutput struct {
PullRequests []PullRequest `json:"pullRequests"`
}
func listPullRequests(_ context.Context, _ *mcp.CallToolRequest, in ListPullRequestsInput) (*mcp.CallToolResult, ListPullRequestsOutput, error) {
return nil, ListPullRequestsOutput{
PullRequests: []PullRequest{
{Number: 42, Title: "Add retry logic to API client", Author: "alice", State: "open"},
{Number: 38, Title: "Fix race condition in cache layer", Author: "bob", State: "open"},
{Number: 35, Title: "Update dependencies to latest", Author: "alice", State: "open"},
},
}, nil
}
type GetDiffInput struct {
Repo string `json:"repo" jsonschema:"repository in owner/name format"`
Number int `json:"number" jsonschema:"pull request number"`
}
type GetDiffOutput struct {
Diff string `json:"diff"`
Files []string `json:"filesChanged"`
Additions int `json:"additions"`
Deletions int `json:"deletions"`
}
func getDiff(_ context.Context, _ *mcp.CallToolRequest, in GetDiffInput) (*mcp.CallToolResult, GetDiffOutput, error) {
return nil, GetDiffOutput{
Diff: "--- a/client.go\n+++ b/client.go\n@@ -45,6 +45,12 @@\n+ for attempt := 0; attempt < maxRetries; attempt++ {\n+ resp, err := c.do(req)\n+ if err == nil { return resp, nil }\n+ time.Sleep(backoff(attempt))\n+ }",
Files: []string{"client.go", "client_test.go"},
Additions: 12,
Deletions: 3,
}, nil
}
type AddReviewCommentInput struct {
Repo string `json:"repo" jsonschema:"repository in owner/name format"`
Number int `json:"number" jsonschema:"pull request number"`
Path string `json:"path" jsonschema:"file path to comment on"`
Line int `json:"line" jsonschema:"line number to comment on"`
Body string `json:"body" jsonschema:"review comment body (markdown)"`
}
type AddReviewCommentOutput struct {
CommentID int `json:"commentId"`
URL string `json:"url"`
}
func addReviewComment(_ context.Context, _ *mcp.CallToolRequest, in AddReviewCommentInput) (*mcp.CallToolResult, AddReviewCommentOutput, error) {
return nil, AddReviewCommentOutput{
CommentID: 1001,
URL: fmt.Sprintf("https://github.com/%s/pull/%d#discussion_r1001", in.Repo, in.Number),
}, nil
}
// -- Project Management variant tools -----------------------------------------
type ListIssuesInput struct {
Repo string `json:"repo" jsonschema:"repository in owner/name format"`
State string `json:"state,omitempty" jsonschema:"filter by state: open, closed, all (default: open)"`
Labels []string `json:"labels,omitempty" jsonschema:"filter by label names"`
}
type Issue struct {
Number int `json:"number"`
Title string `json:"title"`
State string `json:"state"`
Labels []string `json:"labels"`
Assignee string `json:"assignee,omitempty"`
}
type ListIssuesOutput struct {
Issues []Issue `json:"issues"`
}
func listIssues(_ context.Context, _ *mcp.CallToolRequest, in ListIssuesInput) (*mcp.CallToolResult, ListIssuesOutput, error) {
return nil, ListIssuesOutput{
Issues: []Issue{
{Number: 101, Title: "API rate limiting returns wrong status code", State: "open", Labels: []string{"bug", "api"}, Assignee: "alice"},
{Number: 98, Title: "Add support for webhook retries", State: "open", Labels: []string{"enhancement"}, Assignee: ""},
{Number: 95, Title: "Document authentication flow", State: "open", Labels: []string{"documentation"}, Assignee: "carol"},
},
}, nil
}
type CreateIssueInput struct {
Repo string `json:"repo" jsonschema:"repository in owner/name format"`
Title string `json:"title" jsonschema:"issue title"`
Body string `json:"body" jsonschema:"issue body (markdown)"`
Labels []string `json:"labels,omitempty" jsonschema:"labels to apply"`
}
type CreateIssueOutput struct {
Number int `json:"number"`
URL string `json:"url"`
}
func createIssue(_ context.Context, _ *mcp.CallToolRequest, in CreateIssueInput) (*mcp.CallToolResult, CreateIssueOutput, error) {
return nil, CreateIssueOutput{
Number: 102,
URL: fmt.Sprintf("https://github.com/%s/issues/102", in.Repo),
}, nil
}
type AddLabelInput struct {
Repo string `json:"repo" jsonschema:"repository in owner/name format"`
Number int `json:"number" jsonschema:"issue number"`
Labels []string `json:"labels" jsonschema:"labels to add"`
}
type AddLabelOutput struct {
Labels []string `json:"currentLabels"`
}
func addLabel(_ context.Context, _ *mcp.CallToolRequest, in AddLabelInput) (*mcp.CallToolResult, AddLabelOutput, error) {
return nil, AddLabelOutput{
Labels: append([]string{"bug", "api"}, in.Labels...),
}, nil
}
// -- Security variant tools (read-only) ---------------------------------------
type ListSecurityAlertsInput struct {
Repo string `json:"repo" jsonschema:"repository in owner/name format"`
Severity string `json:"severity,omitempty" jsonschema:"filter by severity: critical, high, medium, low (default: all)"`
State string `json:"state,omitempty" jsonschema:"filter by state: open, dismissed, fixed (default: open)"`
}
type SecurityAlert struct {
Number int `json:"number"`
Rule string `json:"rule"`
Severity string `json:"severity"`
State string `json:"state"`
Path string `json:"path"`
}
type ListSecurityAlertsOutput struct {
Alerts []SecurityAlert `json:"alerts"`
}
func listSecurityAlerts(_ context.Context, _ *mcp.CallToolRequest, in ListSecurityAlertsInput) (*mcp.CallToolResult, ListSecurityAlertsOutput, error) {
return nil, ListSecurityAlertsOutput{
Alerts: []SecurityAlert{
{Number: 1, Rule: "sql-injection", Severity: "critical", State: "open", Path: "src/db/query.go"},
{Number: 2, Rule: "hardcoded-secret", Severity: "high", State: "open", Path: "config/settings.go"},
{Number: 3, Rule: "insecure-hash", Severity: "medium", State: "open", Path: "pkg/auth/hash.go"},
},
}, nil
}
type GetAdvisoryInput struct {
AdvisoryID string `json:"advisoryId" jsonschema:"security advisory identifier (e.g. GHSA-xxxx-xxxx-xxxx)"`
}
type GetAdvisoryOutput struct {
ID string `json:"id"`
Summary string `json:"summary"`
Severity string `json:"severity"`
CVEs []string `json:"cves"`
AffectedPkg string `json:"affectedPackage"`
PatchedIn string `json:"patchedIn"`
}
func getAdvisory(_ context.Context, _ *mcp.CallToolRequest, in GetAdvisoryInput) (*mcp.CallToolResult, GetAdvisoryOutput, error) {
return nil, GetAdvisoryOutput{
ID: in.AdvisoryID,
Summary: "Remote code execution via crafted request payload",
Severity: "critical",
CVEs: []string{"CVE-2024-12345"},
AffectedPkg: "example.com/server",
PatchedIn: "v1.2.4",
}, nil
}
// -- CI/CD variant tools ------------------------------------------------------
type ListWorkflowRunsInput struct {
Repo string `json:"repo" jsonschema:"repository in owner/name format"`
Workflow string `json:"workflow,omitempty" jsonschema:"filter by workflow name or filename"`
Status string `json:"status,omitempty" jsonschema:"filter by status: completed, in_progress, queued (default: all)"`
}
type WorkflowRun struct {
ID int `json:"id"`
Workflow string `json:"workflow"`
Status string `json:"status"`
Conclusion string `json:"conclusion,omitempty"`
Branch string `json:"branch"`
}
type ListWorkflowRunsOutput struct {
Runs []WorkflowRun `json:"runs"`
}
func listWorkflowRuns(_ context.Context, _ *mcp.CallToolRequest, in ListWorkflowRunsInput) (*mcp.CallToolResult, ListWorkflowRunsOutput, error) {
return nil, ListWorkflowRunsOutput{
Runs: []WorkflowRun{
{ID: 5001, Workflow: "ci.yml", Status: "completed", Conclusion: "success", Branch: "main"},
{ID: 5002, Workflow: "ci.yml", Status: "in_progress", Branch: "feat/new-api"},
{ID: 5003, Workflow: "deploy.yml", Status: "completed", Conclusion: "failure", Branch: "main"},
},
}, nil
}
type TriggerWorkflowInput struct {
Repo string `json:"repo" jsonschema:"repository in owner/name format"`
Workflow string `json:"workflow" jsonschema:"workflow filename (e.g. ci.yml)"`
Ref string `json:"ref" jsonschema:"git ref to run against (branch, tag, or SHA)"`
Inputs map[string]string `json:"inputs,omitempty" jsonschema:"workflow input parameters"`
}
type TriggerWorkflowOutput struct {
RunID int `json:"runId"`
URL string `json:"url"`
}
func triggerWorkflow(_ context.Context, _ *mcp.CallToolRequest, in TriggerWorkflowInput) (*mcp.CallToolResult, TriggerWorkflowOutput, error) {
return nil, TriggerWorkflowOutput{
RunID: 5004,
URL: fmt.Sprintf("https://github.com/%s/actions/runs/5004", in.Repo),
}, nil
}

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
}

View file

@ -0,0 +1,27 @@
# Research Assistant
A variant-aware MCP server that manages context budget by providing the same research tools (`search_papers`, `get_paper`, `summarize`) at different verbosity levels.
**Pattern demonstrated:** Context budget management with description verbosity control.
## Variants
| Variant | Verbosity | Status | Use Case |
|---|---|---|---|
| `deep-research` | Multi-paragraph with usage examples | Stable | Literature reviews, large context windows |
| `quick-lookup` | Single-sentence descriptions | Stable | Fast Q&A, limited token budgets |
| `synthesis` | Balanced, moderate detail | Experimental | Report generation workflows |
All three variants expose the same three tools with the same behavior — only the description detail level differs.
## Custom Ranking
Clients send a `"contextSize"` hint (`"verbose"`, `"compact"`, or `"standard"`). The ranking function matches the hint to the appropriate variant, falling back to priority order.
## Run
```bash
go run ./examples/server/research
```
The server listens on `http://localhost:8080`.

View file

@ -0,0 +1,139 @@
// Example: Research Assistant — context budget management with the same tools
// at different verbosity levels. Demonstrates how variants can optimize tool
// descriptions for different context window sizes and use cases.
//
// Capability demonstrated: Context budget management, description verbosity.
//
// Variants:
// - deep-research: Verbose multi-paragraph descriptions with usage examples
// - quick-lookup: Concise 1-sentence descriptions for fast Q&A
// - synthesis: Balanced descriptions for report generation
//
// Run:
//
// go run ./examples/server/research
//
// 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() {
// Deep-research variant: verbose descriptions with usage examples
// for thorough research workflows with large context windows.
deepServer := mcp.NewServer(&mcp.Implementation{Name: "research-assistant", Version: "v1.0.0"}, nil)
mcp.AddTool(deepServer, &mcp.Tool{
Name: "search_papers",
Description: "Search academic papers across multiple databases including arXiv, Semantic Scholar, and PubMed. " +
"Use this tool to find relevant literature for a research topic. Supports filtering by year and field. " +
"Returns paper metadata including titles, authors, abstracts, and citation counts. " +
"For best results, use specific technical terms rather than broad queries. " +
"Example queries: 'transformer attention mechanisms in computer vision', " +
"'reinforcement learning from human feedback alignment'. " +
"Follow up with get_paper to retrieve full details for promising results.",
}, searchPapers)
mcp.AddTool(deepServer, &mcp.Tool{
Name: "get_paper",
Description: "Retrieve full details for a specific academic paper by its identifier. " +
"Accepts arXiv IDs (e.g., arxiv:2301.01234), DOIs (e.g., doi:10.1234/5678), or Semantic Scholar IDs. " +
"Returns complete metadata including abstract, references, keywords, and publication venue. " +
"Use this after search_papers to get deeper context on papers of interest. " +
"The references list can be used to find related work by calling get_paper recursively. " +
"Combine with summarize to generate concise summaries of lengthy papers.",
}, getPaper)
mcp.AddTool(deepServer, &mcp.Tool{
Name: "summarize",
Description: "Generate a summary of the provided text using various styles and lengths. " +
"Styles: 'abstract' produces a formal academic abstract, 'bullet-points' extracts key findings " +
"as a structured list, 'executive' creates a high-level overview for non-technical audiences. " +
"Lengths: 'short' (1-2 sentences), 'medium' (1 paragraph), 'long' (2-3 paragraphs). " +
"Best used with paper abstracts or full text retrieved via get_paper. " +
"For multi-paper synthesis, concatenate abstracts and use the 'executive' style.",
}, summarize)
// Quick-lookup variant: concise descriptions for fast Q&A sessions
// where context budget is limited.
quickServer := mcp.NewServer(&mcp.Implementation{Name: "research-assistant", Version: "v1.0.0"}, nil)
mcp.AddTool(quickServer, &mcp.Tool{
Name: "search_papers",
Description: "Search academic papers by query, with optional year and field filters.",
}, searchPapers)
mcp.AddTool(quickServer, &mcp.Tool{
Name: "get_paper",
Description: "Get full details for a paper by its ID (arXiv, DOI, etc.).",
}, getPaper)
mcp.AddTool(quickServer, &mcp.Tool{
Name: "summarize",
Description: "Summarize text in abstract, bullet-point, or executive style.",
}, summarize)
// Synthesis variant: balanced descriptions for report generation
// workflows that need moderate detail.
synthesisServer := mcp.NewServer(&mcp.Implementation{Name: "research-assistant", Version: "v1.0.0"}, nil)
mcp.AddTool(synthesisServer, &mcp.Tool{
Name: "search_papers",
Description: "Search academic papers across arXiv, Semantic Scholar, and PubMed. " +
"Returns titles, authors, abstracts, and citation counts. Filter by year and field.",
}, searchPapers)
mcp.AddTool(synthesisServer, &mcp.Tool{
Name: "get_paper",
Description: "Retrieve full paper details including abstract, references, keywords, and venue. " +
"Accepts arXiv IDs, DOIs, or Semantic Scholar IDs.",
}, getPaper)
mcp.AddTool(synthesisServer, &mcp.Tool{
Name: "summarize",
Description: "Generate a summary in abstract, bullet-point, or executive style. " +
"Configurable length: short (1-2 sentences), medium (paragraph), or long (2-3 paragraphs).",
}, summarize)
vs := variants.NewServer(&mcp.Implementation{Name: "research-assistant", Version: "v1.0.0"}).
WithVariant(variants.ServerVariant{
ID: "deep-research",
Description: "Verbose tool descriptions with usage examples and guidance for thorough research workflows. Best for agents with large context windows performing literature reviews or deep analysis.",
Hints: map[string]string{"contextSize": "verbose", "useCase": "research"},
Status: variants.Stable,
}, deepServer, 0).
WithVariant(variants.ServerVariant{
ID: "quick-lookup",
Description: "Concise 1-sentence tool descriptions for fast question-answering. Minimal context usage for agents with limited token budgets or simple lookup tasks.",
Hints: map[string]string{"contextSize": "compact", "useCase": "qa"},
Status: variants.Stable,
}, quickServer, 1).
WithVariant(variants.ServerVariant{
ID: "synthesis",
Description: "Balanced tool descriptions for report generation and multi-paper synthesis. Moderate detail level suitable for structured writing workflows.",
Hints: map[string]string{"contextSize": "standard", "useCase": "synthesis"},
Status: variants.Experimental,
}, synthesisServer, 2).
// Custom ranking: match by contextSize hint, fall back to priority.
WithRanking(func(_ context.Context, hints variants.VariantHints, vs []variants.ServerVariant) []variants.ServerVariant {
requested, _ := variants.HintValue[string](hints, "contextSize")
slices.SortStableFunc(vs, func(a, b variants.ServerVariant) int {
aMatch := strings.EqualFold(a.Hints["contextSize"], requested)
bMatch := strings.EqualFold(b.Hints["contextSize"], 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,110 @@
package main
import (
"context"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
type SearchPapersInput struct {
Query string `json:"query" jsonschema:"search query for academic papers"`
MaxResults int `json:"maxResults,omitempty" jsonschema:"maximum number of results (1-50, default: 10)"`
Year int `json:"year,omitempty" jsonschema:"filter by publication year"`
Field string `json:"field,omitempty" jsonschema:"filter by field: cs, math, physics, biology, medicine"`
}
type Paper struct {
ID string `json:"id"`
Title string `json:"title"`
Authors []string `json:"authors"`
Year int `json:"year"`
Abstract string `json:"abstract"`
Citations int `json:"citations"`
}
type SearchPapersOutput struct {
Papers []Paper `json:"papers"`
TotalFound int `json:"totalFound"`
}
func searchPapers(_ context.Context, _ *mcp.CallToolRequest, in SearchPapersInput) (*mcp.CallToolResult, SearchPapersOutput, error) {
return nil, SearchPapersOutput{
Papers: []Paper{
{
ID: "arxiv:2301.01234",
Title: "Attention Is All You Need: A Comprehensive Survey",
Authors: []string{"Smith, J.", "Chen, L.", "Kumar, A."},
Year: 2024,
Abstract: "We present a comprehensive survey of transformer architectures and their applications across natural language processing, computer vision, and scientific computing.",
Citations: 342,
},
{
ID: "arxiv:2302.05678",
Title: "Scaling Laws for Neural Language Models",
Authors: []string{"Johnson, R.", "Williams, K."},
Year: 2024,
Abstract: "This paper investigates empirical scaling laws governing the performance of language models as a function of model size, dataset size, and compute budget.",
Citations: 189,
},
},
TotalFound: 1247,
}, nil
}
type GetPaperInput struct {
ID string `json:"id" jsonschema:"paper identifier (e.g. arxiv:2301.01234, doi:10.1234/5678)"`
}
type PaperDetail struct {
ID string `json:"id"`
Title string `json:"title"`
Authors []string `json:"authors"`
Year int `json:"year"`
Abstract string `json:"abstract"`
Citations int `json:"citations"`
References []string `json:"references"`
Keywords []string `json:"keywords"`
Venue string `json:"venue"`
}
type GetPaperOutput struct {
Paper PaperDetail `json:"paper"`
}
func getPaper(_ context.Context, _ *mcp.CallToolRequest, in GetPaperInput) (*mcp.CallToolResult, GetPaperOutput, error) {
return nil, GetPaperOutput{
Paper: PaperDetail{
ID: in.ID,
Title: "Attention Is All You Need: A Comprehensive Survey",
Authors: []string{"Smith, J.", "Chen, L.", "Kumar, A."},
Year: 2024,
Abstract: "We present a comprehensive survey of transformer architectures and their applications across natural language processing, computer vision, and scientific computing.",
Citations: 342,
References: []string{"arxiv:1706.03762", "arxiv:1810.04805", "arxiv:2005.14165"},
Keywords: []string{"transformers", "attention mechanisms", "deep learning", "survey"},
Venue: "NeurIPS 2024",
},
}, nil
}
type SummarizeInput struct {
Text string `json:"text" jsonschema:"text to summarize"`
Style string `json:"style,omitempty" jsonschema:"summary style: abstract, bullet-points, executive (default: abstract)"`
Length string `json:"length,omitempty" jsonschema:"summary length: short, medium, long (default: medium)"`
}
type SummarizeOutput struct {
Summary string `json:"summary"`
Style string `json:"style"`
}
func summarize(_ context.Context, _ *mcp.CallToolRequest, in SummarizeInput) (*mcp.CallToolResult, SummarizeOutput, error) {
style := in.Style
if style == "" {
style = "abstract"
}
return nil, SummarizeOutput{
Summary: "This paper presents key findings in the field, demonstrating significant improvements over prior work through novel methodology and comprehensive evaluation across multiple benchmarks.",
Style: style,
}, nil
}

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
}

View file

@ -0,0 +1,47 @@
// Example: Minimal variant-aware MCP server over stdio. Registers a single
// variant with one tool to demonstrate the simplest possible setup.
//
// Run over stdio:
//
// go run ./examples/server/variants-stdio
package main
import (
"context"
"log"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/modelcontextprotocol/experimental-ext-variants/go/sdk/variants"
)
type GreetInput struct {
Name string `json:"name" jsonschema:"name to greet"`
}
type GreetOutput struct {
Message string `json:"message"`
}
func greet(_ context.Context, _ *mcp.CallToolRequest, in GreetInput) (*mcp.CallToolResult, GreetOutput, error) {
return nil, GreetOutput{Message: "Hello, " + in.Name + "!"}, nil
}
func main() {
inner := mcp.NewServer(&mcp.Implementation{Name: "greeter", Version: "v1.0.0"}, nil)
mcp.AddTool(inner, &mcp.Tool{
Name: "greet",
Description: "Greet someone by name",
}, greet)
vs := variants.NewServer(&mcp.Implementation{Name: "greeter", Version: "v1.0.0"}).
WithVariant(variants.ServerVariant{
ID: "default",
Description: "Default greeting variant.",
Status: variants.Stable,
}, inner, 0)
if err := vs.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
log.Fatal(err)
}
}