Merge branch 'main' into mcp-client-go
This commit is contained in:
commit
9acd962cbf
8 changed files with 295 additions and 12 deletions
6
mcp-client-python/uv.lock
generated
6
mcp-client-python/uv.lock
generated
|
|
@ -671,11 +671,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.20"
|
||||
version = "0.0.22"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
6
mcp-client-typescript/package-lock.json
generated
6
mcp-client-typescript/package-lock.json
generated
|
|
@ -806,9 +806,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.11.4",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz",
|
||||
"integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==",
|
||||
"version": "4.11.7",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz",
|
||||
"integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
|
|
|
|||
19
weather-server-go/README.md
Normal file
19
weather-server-go/README.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# A Simple MCP Weather Server written in Go
|
||||
|
||||
See the [Quickstart](https://modelcontextprotocol.io/quickstart) tutorial for more information.
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
go build -o weather
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
./weather
|
||||
```
|
||||
|
||||
The server will communicate via stdio and expose two MCP tools:
|
||||
- `get_forecast` - Get weather forecast for a location (requires latitude and longitude)
|
||||
- `get_alerts` - Get weather alerts for a US state (requires two-letter state code)
|
||||
10
weather-server-go/go.mod
Normal file
10
weather-server-go/go.mod
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
module github.com/modelcontextprotocol/quickstart-resources/weather-server-go
|
||||
|
||||
go 1.25.1
|
||||
|
||||
require github.com/modelcontextprotocol/go-sdk v1.0.0
|
||||
|
||||
require (
|
||||
github.com/google/jsonschema-go v0.3.0 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
)
|
||||
10
weather-server-go/go.sum
Normal file
10
weather-server-go/go.sum
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
|
||||
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/modelcontextprotocol/go-sdk v1.0.0 h1:Z4MSjLi38bTgLrd/LjSmofqRqyBiVKRyQSJgw8q8V74=
|
||||
github.com/modelcontextprotocol/go-sdk v1.0.0/go.mod h1:nYtYQroQ2KQiM0/SbyEPUWQ6xs4B95gJjEalc9AQyOs=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
244
weather-server-go/main.go
Normal file
244
weather-server-go/main.go
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
const (
|
||||
NWSAPIBase = "https://api.weather.gov"
|
||||
UserAgent = "weather-app/1.0"
|
||||
)
|
||||
|
||||
type ForecastInput struct {
|
||||
Latitude float64 `json:"latitude" jsonschema:"Latitude of the location"`
|
||||
Longitude float64 `json:"longitude" jsonschema:"Longitude of the location"`
|
||||
}
|
||||
|
||||
type AlertsInput struct {
|
||||
State string `json:"state" jsonschema:"Two-letter US state code (e.g. CA, NY)"`
|
||||
}
|
||||
|
||||
type PointsResponse struct {
|
||||
Properties struct {
|
||||
Forecast string `json:"forecast"`
|
||||
} `json:"properties"`
|
||||
}
|
||||
|
||||
type ForecastResponse struct {
|
||||
Properties struct {
|
||||
Periods []ForecastPeriod `json:"periods"`
|
||||
} `json:"properties"`
|
||||
}
|
||||
|
||||
type ForecastPeriod struct {
|
||||
Name string `json:"name"`
|
||||
Temperature int `json:"temperature"`
|
||||
TemperatureUnit string `json:"temperatureUnit"`
|
||||
WindSpeed string `json:"windSpeed"`
|
||||
WindDirection string `json:"windDirection"`
|
||||
DetailedForecast string `json:"detailedForecast"`
|
||||
}
|
||||
|
||||
type AlertsResponse struct {
|
||||
Features []AlertFeature `json:"features"`
|
||||
}
|
||||
|
||||
type AlertFeature struct {
|
||||
Properties AlertProperties `json:"properties"`
|
||||
}
|
||||
|
||||
type AlertProperties struct {
|
||||
Event string `json:"event"`
|
||||
AreaDesc string `json:"areaDesc"`
|
||||
Severity string `json:"severity"`
|
||||
Description string `json:"description"`
|
||||
Instruction string `json:"instruction"`
|
||||
}
|
||||
|
||||
func makeNWSRequest[T any](ctx context.Context, url string) (*T, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", UserAgent)
|
||||
req.Header.Set("Accept", "application/geo+json")
|
||||
|
||||
client := http.DefaultClient
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to make request to %s: %w", url, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var result T
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func formatAlert(alert AlertFeature) string {
|
||||
props := alert.Properties
|
||||
event := cmp.Or(props.Event, "Unknown")
|
||||
areaDesc := cmp.Or(props.AreaDesc, "Unknown")
|
||||
severity := cmp.Or(props.Severity, "Unknown")
|
||||
description := cmp.Or(props.Description, "No description available")
|
||||
instruction := cmp.Or(props.Instruction, "No specific instructions provided")
|
||||
|
||||
return fmt.Sprintf(`
|
||||
Event: %s
|
||||
Area: %s
|
||||
Severity: %s
|
||||
Description: %s
|
||||
Instructions: %s
|
||||
`, event, areaDesc, severity, description, instruction)
|
||||
}
|
||||
|
||||
func formatPeriod(period ForecastPeriod) string {
|
||||
return fmt.Sprintf(`
|
||||
%s:
|
||||
Temperature: %d°%s
|
||||
Wind: %s %s
|
||||
Forecast: %s
|
||||
`, period.Name, period.Temperature, period.TemperatureUnit,
|
||||
period.WindSpeed, period.WindDirection, period.DetailedForecast)
|
||||
}
|
||||
|
||||
func getForecast(ctx context.Context, req *mcp.CallToolRequest, input ForecastInput) (
|
||||
*mcp.CallToolResult, any, error,
|
||||
) {
|
||||
// Get points data
|
||||
pointsURL := fmt.Sprintf("%s/points/%f,%f", NWSAPIBase, input.Latitude, input.Longitude)
|
||||
pointsData, err := makeNWSRequest[PointsResponse](ctx, pointsURL)
|
||||
if err != nil {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
&mcp.TextContent{Text: "Unable to fetch forecast data for this location."},
|
||||
},
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
// Get forecast data
|
||||
forecastURL := pointsData.Properties.Forecast
|
||||
if forecastURL == "" {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
&mcp.TextContent{Text: "Unable to fetch forecast URL."},
|
||||
},
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
forecastData, err := makeNWSRequest[ForecastResponse](ctx, forecastURL)
|
||||
if err != nil {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
&mcp.TextContent{Text: "Unable to fetch detailed forecast."},
|
||||
},
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
// Format the periods
|
||||
periods := forecastData.Properties.Periods
|
||||
if len(periods) == 0 {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
&mcp.TextContent{Text: "No forecast periods available."},
|
||||
},
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
// Show next 5 periods
|
||||
var forecasts []string
|
||||
for i := range min(5, len(periods)) {
|
||||
forecasts = append(forecasts, formatPeriod(periods[i]))
|
||||
}
|
||||
|
||||
result := strings.Join(forecasts, "\n---\n")
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
&mcp.TextContent{Text: result},
|
||||
},
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
func getAlerts(ctx context.Context, req *mcp.CallToolRequest, input AlertsInput) (
|
||||
*mcp.CallToolResult, any, error,
|
||||
) {
|
||||
// Build alerts URL
|
||||
stateCode := strings.ToUpper(input.State)
|
||||
alertsURL := fmt.Sprintf("%s/alerts/active/area/%s", NWSAPIBase, stateCode)
|
||||
|
||||
alertsData, err := makeNWSRequest[AlertsResponse](ctx, alertsURL)
|
||||
if err != nil {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
&mcp.TextContent{Text: "Unable to fetch alerts or no alerts found."},
|
||||
},
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
// Check if there are any alerts
|
||||
if len(alertsData.Features) == 0 {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
&mcp.TextContent{Text: "No active alerts for this state."},
|
||||
},
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
// Format alerts
|
||||
var alerts []string
|
||||
for _, feature := range alertsData.Features {
|
||||
alerts = append(alerts, formatAlert(feature))
|
||||
}
|
||||
|
||||
result := strings.Join(alerts, "\n---\n")
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
&mcp.TextContent{Text: result},
|
||||
},
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Create MCP server
|
||||
server := mcp.NewServer(&mcp.Implementation{
|
||||
Name: "weather",
|
||||
Version: "1.0.0",
|
||||
}, nil)
|
||||
|
||||
// Add get_forecast tool
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "get_forecast",
|
||||
Description: "Get weather forecast for a location",
|
||||
}, getForecast)
|
||||
|
||||
// Add get_alerts tool
|
||||
mcp.AddTool(server, &mcp.Tool{
|
||||
Name: "get_alerts",
|
||||
Description: "Get weather alerts for a US state",
|
||||
}, getAlerts)
|
||||
|
||||
// Run server on stdio transport
|
||||
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
6
weather-server-python/uv.lock
generated
6
weather-server-python/uv.lock
generated
|
|
@ -548,11 +548,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.20"
|
||||
version = "0.0.22"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
6
weather-server-typescript/package-lock.json
generated
6
weather-server-typescript/package-lock.json
generated
|
|
@ -544,9 +544,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.11.4",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz",
|
||||
"integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==",
|
||||
"version": "4.11.7",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz",
|
||||
"integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue