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:
parent
c01fd188d2
commit
e1c17e047f
3 changed files with 1218 additions and 147 deletions
27
go/sdk/examples/server/research/README.md
Normal file
27
go/sdk/examples/server/research/README.md
Normal 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`.
|
||||
139
go/sdk/examples/server/research/main.go
Normal file
139
go/sdk/examples/server/research/main.go
Normal 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))
|
||||
}
|
||||
110
go/sdk/examples/server/research/tools.go
Normal file
110
go/sdk/examples/server/research/tools.go
Normal 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
|
||||
}
|
||||
Loading…
Reference in a new issue