From 2fad83fe9a9cc3af1bbbfdf60ed59e1cf8282667 Mon Sep 17 00:00:00 2001 From: Christophe Vila Date: Sat, 25 Jul 2026 21:27:19 +0200 Subject: [PATCH] chore: drop the mcp-go dependency and remove mcpspike throwaway spike Nothing in the backend speaks MCP anymore -- internal/garmin talks to its embedded Python wrapper over plain JSON-lines instead. The cmd/mcpspike directory was a temporary spike for validating the mcp-go client, which is no longer needed. Co-Authored-By: Claude Sonnet 5 --- backend/cmd/mcpspike/main.go | 243 ----------------------------------- backend/go.mod | 6 - backend/go.sum | 32 ----- 3 files changed, 281 deletions(-) delete mode 100644 backend/cmd/mcpspike/main.go diff --git a/backend/cmd/mcpspike/main.go b/backend/cmd/mcpspike/main.go deleted file mode 100644 index 94ebfdc..0000000 --- a/backend/cmd/mcpspike/main.go +++ /dev/null @@ -1,243 +0,0 @@ -// Throwaway spike to validate that mark3labs/mcp-go's stdio client can drive -// mcp-garmin (spawn, initialize handshake, call tools, parse results). -// Not part of the production build — delete once internal/garmin is built. -package main - -import ( - "context" - "encoding/json" - "flag" - "fmt" - "log" - "os" - "strconv" - "time" - - "github.com/mark3labs/mcp-go/client" - "github.com/mark3labs/mcp-go/client/transport" - "github.com/mark3labs/mcp-go/mcp" -) - -func main() { - pythonPath := flag.String("python", "", "path to mcp-garmin .venv python executable") - serverPath := flag.String("server", "", "path to mcp-garmin server.py") - limit := flag.Int("limit", 5, "activity limit for get_activities") - startDate := flag.String("start", "", "start date YYYY-MM-DD for get_activities (default: 365 days ago)") - endDate := flag.String("end", "", "end date YYYY-MM-DD for get_activities (default: today)") - flag.Parse() - - if *pythonPath == "" || *serverPath == "" { - log.Fatal("usage: mcpspike -python -server ") - } - - email := os.Getenv("GARMIN_EMAIL") - password := os.Getenv("GARMIN_PASSWORD") - if email == "" || password == "" { - log.Fatal("GARMIN_EMAIL and GARMIN_PASSWORD must be set in the environment") - } - - env := []string{ - "GARMIN_EMAIL=" + email, - "GARMIN_PASSWORD=" + password, - "PYTHONUNBUFFERED=1", - } - - c, err := client.NewStdioMCPClient(*pythonPath, env, *serverPath) - if err != nil { - log.Fatalf("spawn subprocess: %v", err) - } - defer c.Close() - - if stdio, ok := c.GetTransport().(*transport.Stdio); ok { - go func() { - buf := make([]byte, 4096) - for { - n, err := stdio.Stderr().Read(buf) - if n > 0 { - fmt.Fprint(os.Stderr, string(buf[:n])) - } - if err != nil { - return - } - } - }() - } else { - log.Println("warning: could not get stdio transport to forward subprocess stderr") - } - - ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) - defer cancel() - - initReq := mcp.InitializeRequest{} - initReq.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION - initReq.Params.ClientInfo = mcp.Implementation{Name: "mcpspike", Version: "0.0.1"} - - initRes, err := c.Initialize(ctx, initReq) - if err != nil { - log.Fatalf("initialize handshake failed: %v", err) - } - fmt.Printf("initialized OK: server=%s version=%s protocol=%s\n", - initRes.ServerInfo.Name, initRes.ServerInfo.Version, initRes.ProtocolVersion) - - tools, err := c.ListTools(ctx, mcp.ListToolsRequest{}) - if err != nil { - log.Fatalf("list tools failed: %v", err) - } - fmt.Printf("server exposes %d tools:\n", len(tools.Tools)) - for _, t := range tools.Tools { - fmt.Printf(" - %s: %s\n", t.Name, t.Description) - } - - fmt.Println("\ncalling authenticate()...") - authRes, err := callTool(ctx, c, "authenticate", nil) - if err != nil { - log.Fatalf("authenticate call failed: %v", err) - } - fmt.Printf("authenticate() -> %s\n", authRes) - - if containsMFAPrompt(authRes) { - fmt.Print("MFA required. Enter code: ") - var code string - fmt.Scanln(&code) - mfaRes, err := callTool(ctx, c, "complete_mfa", map[string]any{"code": code}) - if err != nil { - log.Fatalf("complete_mfa call failed: %v", err) - } - fmt.Printf("complete_mfa() -> %s\n", mfaRes) - } - - start := *startDate - if start == "" { - start = time.Now().AddDate(0, 0, -365).Format("2006-01-02") - } - end := *endDate - if end == "" { - end = time.Now().Format("2006-01-02") - } - - fmt.Printf("\ncalling get_activities(start_date=%s, end_date=%s, limit=%d)...\n", start, end, *limit) - actRes, err := callTool(ctx, c, "get_activities", map[string]any{ - "start_date": start, - "end_date": end, - "limit": *limit, - }) - if err != nil { - log.Fatalf("get_activities call failed: %v", err) - } - - var activities []map[string]any - if err := json.Unmarshal([]byte(actRes), &activities); err != nil { - fmt.Printf("get_activities() returned non-JSON (likely an error string): %s\n", actRes) - return - } - pretty, _ := json.MarshalIndent(activities, "", " ") - fmt.Printf("get_activities() parsed OK, %d activities, %d bytes:\n%s\n", len(activities), len(actRes), truncate(string(pretty), 4000)) - - if len(activities) == 0 { - fmt.Println("\nno activities in range, skipping get_activity_details") - return - } - - // Prefer a running activity if one is present, for the most relevant lap/split shape. - chosen := activities[0] - for _, a := range activities { - if at, ok := a["activityType"].(map[string]any); ok { - if tk, _ := at["typeKey"].(string); tk == "running" || tk == "trail_running" || tk == "treadmill_running" { - chosen = a - break - } - } - } - var activityID string - switch v := chosen["activityId"].(type) { - case float64: - activityID = strconv.FormatFloat(v, 'f', -1, 64) - default: - activityID = fmt.Sprint(v) - } - fmt.Printf("\ncalling get_activity_details(activity_id=%s) [activityName=%v]...\n", activityID, chosen["activityName"]) - - splitsRes, err := callTool(ctx, c, "get_activity_splits", map[string]any{"activity_id": activityID}) - if err != nil { - log.Fatalf("get_activity_splits call failed: %v", err) - } - - var splits map[string]any - if err := json.Unmarshal([]byte(splitsRes), &splits); err != nil { - fmt.Printf("get_activity_splits() returned non-JSON: %s\n", splitsRes) - return - } - keys := make([]string, 0, len(splits)) - for k := range splits { - keys = append(keys, k) - } - fmt.Printf("get_activity_splits() top-level keys: %v\n", keys) - prettySplits, _ := json.MarshalIndent(splits, "", " ") - fmt.Printf("get_activity_splits() parsed OK, %d bytes:\n%s\n", len(splitsRes), truncate(string(prettySplits), 8000)) - - fmt.Println("\ncalling get_activity_details() again to inspect metricDescriptors + a small metrics sample...") - detailsRes, err := callTool(ctx, c, "get_activity_details", map[string]any{"activity_id": activityID}) - if err != nil { - log.Fatalf("get_activity_details call failed: %v", err) - } - var details map[string]any - if err := json.Unmarshal([]byte(detailsRes), &details); err != nil { - fmt.Printf("get_activity_details() returned non-JSON: %s\n", detailsRes) - return - } - if descriptors, ok := details["metricDescriptors"]; ok { - pretty, _ := json.MarshalIndent(descriptors, "", " ") - fmt.Printf("metricDescriptors:\n%s\n", pretty) - } else { - fmt.Println("no metricDescriptors key found") - } - if rows, ok := details["activityDetailMetrics"].([]any); ok && len(rows) > 0 { - sampleN := 5 - if len(rows) < sampleN { - sampleN = len(rows) - } - pretty, _ := json.MarshalIndent(rows[:sampleN], "", " ") - fmt.Printf("activityDetailMetrics sample (first %d of %d rows):\n%s\n", sampleN, len(rows), pretty) - } -} - -func callTool(ctx context.Context, c *client.Client, name string, args map[string]any) (string, error) { - req := mcp.CallToolRequest{} - req.Params.Name = name - req.Params.Arguments = args - - res, err := c.CallTool(ctx, req) - if err != nil { - return "", err - } - if res.IsError { - return "", fmt.Errorf("tool %s returned an error result", name) - } - var out string - for _, content := range res.Content { - if tc, ok := content.(mcp.TextContent); ok { - out += tc.Text - } - } - return out, nil -} - -func containsMFAPrompt(s string) bool { - for _, needle := range []string{"MFA required", "MFA code", "complete_mfa"} { - if len(s) >= len(needle) { - for i := 0; i+len(needle) <= len(s); i++ { - if s[i:i+len(needle)] == needle { - return true - } - } - } - } - return false -} - -func truncate(s string, n int) string { - if len(s) <= n { - return s - } - return s[:n] + "...(truncated)" -} diff --git a/backend/go.mod b/backend/go.mod index 2f825a1..4eefd8e 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -6,7 +6,6 @@ require ( github.com/coreos/go-oidc/v3 v3.20.0 github.com/go-chi/chi/v5 v5.3.1 github.com/golang-jwt/jwt/v5 v5.3.1 - github.com/mark3labs/mcp-go v0.56.0 golang.org/x/oauth2 v0.36.0 modernc.org/sqlite v1.53.0 ) @@ -14,16 +13,11 @@ require ( require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect - github.com/spf13/cast v1.7.1 // indirect - github.com/yosida95/uritemplate/v3 v3.0.2 // indirect golang.org/x/sys v0.44.0 // indirect - golang.org/x/text v0.14.0 // indirect modernc.org/libc v1.73.4 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index 8ba9ad9..b4a698a 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,53 +1,25 @@ github.com/coreos/go-oidc/v3 v3.20.0 h1:EtE0WIBHk03N+DqGkY4+UONzzZHk7amKt6IyNd7OsZE= github.com/coreos/go-oidc/v3 v3.20.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= -github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8= github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= -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.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mark3labs/mcp-go v0.56.0 h1:7aCj2wODCskMi08f923ADG+EfELZBdiKILny415cIS8= -github.com/mark3labs/mcp-go v0.56.0/go.mod h1:+8WclSK1ZUweCP3hvktSji8n8ABG/95QaEkeVE/Uwas= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -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/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= @@ -57,12 +29,8 @@ golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c= modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws=