|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "fmt" |
| 6 | + "time" |
| 7 | + |
| 8 | + "github.com/mark3labs/mcp-go/mcp" |
| 9 | + "github.com/mark3labs/mcp-go/server" |
| 10 | +) |
| 11 | + |
| 12 | +// Note: The jsonschema_description tag is added to the JSON schema as description |
| 13 | +// Ideally use better descriptions, this is just an example |
| 14 | +type WeatherRequest struct { |
| 15 | + Location string `json:"location" jsonschema_description:"City or location"` |
| 16 | + Units string `json:"units,omitempty" jsonschema_description:"celsius or fahrenheit"` |
| 17 | +} |
| 18 | + |
| 19 | +type WeatherResponse struct { |
| 20 | + Location string `json:"location" jsonschema_description:"Location"` |
| 21 | + Temperature float64 `json:"temperature" jsonschema_description:"Temperature"` |
| 22 | + Units string `json:"units" jsonschema_description:"Units"` |
| 23 | + Conditions string `json:"conditions" jsonschema_description:"Weather conditions"` |
| 24 | + Timestamp time.Time `json:"timestamp" jsonschema_description:"When retrieved"` |
| 25 | +} |
| 26 | + |
| 27 | +type UserProfile struct { |
| 28 | + ID string `json:"id" jsonschema_description:"User ID"` |
| 29 | + Name string `json:"name" jsonschema_description:"Full name"` |
| 30 | + Email string `json:"email" jsonschema_description:"Email"` |
| 31 | + Tags []string `json:"tags" jsonschema_description:"User tags"` |
| 32 | +} |
| 33 | + |
| 34 | +type UserRequest struct { |
| 35 | + UserID string `json:"userId" jsonschema_description:"User ID"` |
| 36 | +} |
| 37 | + |
| 38 | +type Asset struct { |
| 39 | + ID string `json:"id" jsonschema_description:"Asset identifier"` |
| 40 | + Name string `json:"name" jsonschema_description:"Asset name"` |
| 41 | + Value float64 `json:"value" jsonschema_description:"Current value"` |
| 42 | + Currency string `json:"currency" jsonschema_description:"Currency code"` |
| 43 | +} |
| 44 | + |
| 45 | +type AssetListRequest struct { |
| 46 | + Limit int `json:"limit,omitempty" jsonschema_description:"Number of assets to return"` |
| 47 | +} |
| 48 | + |
| 49 | +func main() { |
| 50 | + s := server.NewMCPServer( |
| 51 | + "Structured Output Example", |
| 52 | + "1.0.0", |
| 53 | + server.WithToolCapabilities(false), |
| 54 | + ) |
| 55 | + |
| 56 | + // Example 1: Auto-generated schema from struct |
| 57 | + weatherTool := mcp.NewTool("get_weather", |
| 58 | + mcp.WithDescription("Get weather with structured output"), |
| 59 | + mcp.WithOutputSchema[WeatherResponse](), |
| 60 | + mcp.WithString("location", mcp.Required()), |
| 61 | + mcp.WithString("units", mcp.Enum("celsius", "fahrenheit"), mcp.DefaultString("celsius")), |
| 62 | + ) |
| 63 | + s.AddTool(weatherTool, mcp.NewStructuredToolHandler(getWeatherHandler)) |
| 64 | + |
| 65 | + // Example 2: Nested struct schema |
| 66 | + userTool := mcp.NewTool("get_user_profile", |
| 67 | + mcp.WithDescription("Get user profile"), |
| 68 | + mcp.WithOutputSchema[UserProfile](), |
| 69 | + mcp.WithString("userId", mcp.Required()), |
| 70 | + ) |
| 71 | + s.AddTool(userTool, mcp.NewStructuredToolHandler(getUserProfileHandler)) |
| 72 | + |
| 73 | + // Example 3: Array output - direct array of objects |
| 74 | + assetsTool := mcp.NewTool("get_assets", |
| 75 | + mcp.WithDescription("Get list of assets as array"), |
| 76 | + mcp.WithOutputSchema[[]Asset](), |
| 77 | + mcp.WithNumber("limit", mcp.Min(1), mcp.Max(100), mcp.DefaultNumber(10)), |
| 78 | + ) |
| 79 | + s.AddTool(assetsTool, mcp.NewStructuredToolHandler(getAssetsHandler)) |
| 80 | + |
| 81 | + // Example 4: Manual result creation |
| 82 | + manualTool := mcp.NewTool("manual_structured", |
| 83 | + mcp.WithDescription("Manual structured result"), |
| 84 | + mcp.WithOutputSchema[WeatherResponse](), |
| 85 | + mcp.WithString("location", mcp.Required()), |
| 86 | + ) |
| 87 | + s.AddTool(manualTool, mcp.NewTypedToolHandler(manualWeatherHandler)) |
| 88 | + |
| 89 | + if err := server.ServeStdio(s); err != nil { |
| 90 | + fmt.Printf("Server error: %v\n", err) |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +func getWeatherHandler(ctx context.Context, request mcp.CallToolRequest, args WeatherRequest) (WeatherResponse, error) { |
| 95 | + temp := 22.5 |
| 96 | + if args.Units == "fahrenheit" { |
| 97 | + temp = temp*9/5 + 32 |
| 98 | + } |
| 99 | + |
| 100 | + return WeatherResponse{ |
| 101 | + Location: args.Location, |
| 102 | + Temperature: temp, |
| 103 | + Units: args.Units, |
| 104 | + Conditions: "Cloudy with a chance of meatballs", |
| 105 | + Timestamp: time.Now(), |
| 106 | + }, nil |
| 107 | +} |
| 108 | + |
| 109 | +func getUserProfileHandler(ctx context.Context, request mcp.CallToolRequest, args UserRequest) (UserProfile, error) { |
| 110 | + return UserProfile{ |
| 111 | + ID: args.UserID, |
| 112 | + Name: "John Doe", |
| 113 | + |
| 114 | + Tags: []string{"developer", "golang"}, |
| 115 | + }, nil |
| 116 | +} |
| 117 | + |
| 118 | +func getAssetsHandler(ctx context.Context, request mcp.CallToolRequest, args AssetListRequest) ([]Asset, error) { |
| 119 | + limit := args.Limit |
| 120 | + if limit <= 0 { |
| 121 | + limit = 10 |
| 122 | + } |
| 123 | + |
| 124 | + assets := []Asset{ |
| 125 | + {ID: "btc", Name: "Bitcoin", Value: 45000.50, Currency: "USD"}, |
| 126 | + {ID: "eth", Name: "Ethereum", Value: 3200.75, Currency: "USD"}, |
| 127 | + {ID: "ada", Name: "Cardano", Value: 0.85, Currency: "USD"}, |
| 128 | + {ID: "sol", Name: "Solana", Value: 125.30, Currency: "USD"}, |
| 129 | + {ID: "dot", Name: "Pottedot", Value: 18.45, Currency: "USD"}, |
| 130 | + } |
| 131 | + |
| 132 | + if limit > len(assets) { |
| 133 | + limit = len(assets) |
| 134 | + } |
| 135 | + |
| 136 | + return assets[:limit], nil |
| 137 | +} |
| 138 | + |
| 139 | +func manualWeatherHandler(ctx context.Context, request mcp.CallToolRequest, args WeatherRequest) (*mcp.CallToolResult, error) { |
| 140 | + response := WeatherResponse{ |
| 141 | + Location: args.Location, |
| 142 | + Temperature: 25.0, |
| 143 | + Units: "celsius", |
| 144 | + Conditions: "Sunny, yesterday my life was filled with rain", |
| 145 | + Timestamp: time.Now(), |
| 146 | + } |
| 147 | + |
| 148 | + fallbackText := fmt.Sprintf("Weather in %s: %.1f°C, %s", |
| 149 | + response.Location, response.Temperature, response.Conditions) |
| 150 | + |
| 151 | + return mcp.NewToolResultStructured(response, fallbackText), nil |
| 152 | +} |
0 commit comments