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
16 changed files with 1218 additions and 147 deletions
28
README.md
28
README.md
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
30
go/sdk/examples/server/github/README.md
Normal file
30
go/sdk/examples/server/github/README.md
Normal 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.
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
246
go/sdk/examples/server/github/tools.go
Normal file
246
go/sdk/examples/server/github/tools.go
Normal 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
|
||||||
|
}
|
||||||
118
go/sdk/examples/server/model-optimized/README.md
Normal file
118
go/sdk/examples/server/model-optimized/README.md
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
116
go/sdk/examples/server/model-optimized/main.go
Normal file
116
go/sdk/examples/server/model-optimized/main.go
Normal 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))
|
||||||
|
}
|
||||||
91
go/sdk/examples/server/model-optimized/tools.go
Normal file
91
go/sdk/examples/server/model-optimized/tools.go
Normal 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
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
36
go/sdk/examples/server/trading/README.md
Normal file
36
go/sdk/examples/server/trading/README.md
Normal 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`.
|
||||||
130
go/sdk/examples/server/trading/main.go
Normal file
130
go/sdk/examples/server/trading/main.go
Normal 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))
|
||||||
|
}
|
||||||
296
go/sdk/examples/server/trading/tools.go
Normal file
296
go/sdk/examples/server/trading/tools.go
Normal 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
|
||||||
|
}
|
||||||
47
go/sdk/examples/server/variants-stdio/main.go
Normal file
47
go/sdk/examples/server/variants-stdio/main.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue