go.go•6.29 kB
package handlers
import (
"context"
"encoding/json"
"fmt"
"sort"
"strings"
"sync"
"github.com/mark3labs/mcp-go/mcp"
"github.com/sirupsen/logrus"
)
const (
// GoProxyURL is the base URL for the Go proxy API
GoProxyURL = "https://proxy.golang.org"
)
// GoHandler handles Go package version checking
type GoHandler struct {
client HTTPClient
cache *sync.Map
logger *logrus.Logger
}
// NewGoHandler creates a new Go handler
func NewGoHandler(logger *logrus.Logger, cache *sync.Map) *GoHandler {
if cache == nil {
cache = &sync.Map{}
}
return &GoHandler{
client: DefaultHTTPClient,
cache: cache,
logger: logger,
}
}
// GoModuleInfo represents information about a Go module
type GoModuleInfo struct {
Version string `json:"Version"`
Time string `json:"Time"`
Versions []string `json:"Versions"`
}
// getLatestVersion gets the latest version of a Go module
func (h *GoHandler) getLatestVersion(modulePath string) (string, error) {
// Check cache first
if cachedVersion, ok := h.cache.Load(fmt.Sprintf("go:%s", modulePath)); ok {
h.logger.WithField("module", modulePath).Debug("Using cached Go module version")
return cachedVersion.(string), nil
}
// Construct URL
moduleURL := fmt.Sprintf("%s/%s/@latest", GoProxyURL, modulePath)
h.logger.WithFields(logrus.Fields{
"module": modulePath,
"url": moduleURL,
}).Debug("Fetching Go module info")
// Make request
body, err := MakeRequestWithLogger(h.client, h.logger, "GET", moduleURL, nil)
if err != nil {
return "", fmt.Errorf("failed to fetch Go module info: %w", err)
}
// Parse response
var info GoModuleInfo
if err := json.Unmarshal(body, &info); err != nil {
return "", fmt.Errorf("failed to parse Go module info: %w", err)
}
// Cache result
h.cache.Store(fmt.Sprintf("go:%s", modulePath), info.Version)
return info.Version, nil
}
// GetLatestVersion gets the latest version of Go packages
func (h *GoHandler) GetLatestVersion(ctx context.Context, args map[string]interface{}) (*mcp.CallToolResult, error) {
h.logger.Debug("Getting latest Go package versions")
// Parse dependencies
depsRaw, ok := args["dependencies"]
if !ok {
return nil, fmt.Errorf("missing required parameter: dependencies")
}
// Always set a default module name
goModule := GoModule{
Module: "github.com/sammcj/mcp-package-version",
}
// Log the raw dependencies for debugging
h.logger.WithField("dependencies", fmt.Sprintf("%+v", depsRaw)).Debug("Raw dependencies")
// Handle different input formats
if depsMap, ok := depsRaw.(map[string]interface{}); ok {
// Check if this is the complex format with a module field
if moduleName, ok := depsMap["module"].(string); ok {
goModule.Module = moduleName
// Parse require
if requireRaw, ok := depsMap["require"].([]interface{}); ok {
for _, reqRaw := range requireRaw {
if reqMap, ok := reqRaw.(map[string]interface{}); ok {
var req GoRequire
if path, ok := reqMap["path"].(string); ok {
req.Path = path
} else {
continue
}
if version, ok := reqMap["version"].(string); ok {
req.Version = version
}
goModule.Require = append(goModule.Require, req)
}
}
}
// Parse replace
if replaceRaw, ok := depsMap["replace"].([]interface{}); ok {
for _, repRaw := range replaceRaw {
if repMap, ok := repRaw.(map[string]interface{}); ok {
var rep GoReplace
if old, ok := repMap["old"].(string); ok {
rep.Old = old
} else {
continue
}
if new, ok := repMap["new"].(string); ok {
rep.New = new
} else {
continue
}
if version, ok := repMap["version"].(string); ok {
rep.Version = version
}
goModule.Replace = append(goModule.Replace, rep)
}
}
}
} else {
// Simple format: key-value pairs are dependencies
for path, versionRaw := range depsMap {
h.logger.WithFields(logrus.Fields{
"path": path,
"version": versionRaw,
}).Debug("Processing dependency")
if version, ok := versionRaw.(string); ok {
goModule.Require = append(goModule.Require, GoRequire{
Path: path,
Version: version,
})
}
}
}
} else {
return nil, fmt.Errorf("invalid dependencies format: expected object, got %T", depsRaw)
}
// Log the parsed module for debugging
h.logger.WithField("module", fmt.Sprintf("%+v", goModule)).Debug("Parsed module")
// Process each require dependency
results := make([]PackageVersion, 0, len(goModule.Require))
for _, req := range goModule.Require {
h.logger.WithFields(logrus.Fields{
"module": req.Path,
"version": req.Version,
}).Debug("Processing Go module")
// Check if module is replaced
var isReplaced bool
var replacedBy string
var replacedVersion string
for _, rep := range goModule.Replace {
if rep.Old == req.Path {
isReplaced = true
replacedBy = rep.New
replacedVersion = rep.Version
break
}
}
// If module is replaced, use the replacement
if isReplaced {
results = append(results, PackageVersion{
Name: req.Path,
CurrentVersion: StringPtr(req.Version),
LatestVersion: fmt.Sprintf("replaced by %s@%s", replacedBy, replacedVersion),
Registry: "go",
Skipped: true,
SkipReason: "Module is replaced",
})
continue
}
// Get latest version
latestVersion, err := h.getLatestVersion(req.Path)
if err != nil {
h.logger.WithFields(logrus.Fields{
"module": req.Path,
"error": err.Error(),
}).Error("Failed to get Go module info")
results = append(results, PackageVersion{
Name: req.Path,
CurrentVersion: StringPtr(req.Version),
LatestVersion: "unknown",
Registry: "go",
Skipped: true,
SkipReason: fmt.Sprintf("Failed to fetch module info: %v", err),
})
continue
}
// Add result
results = append(results, PackageVersion{
Name: req.Path,
CurrentVersion: StringPtr(req.Version),
LatestVersion: latestVersion,
Registry: "go",
})
}
// Sort results by name
sort.Slice(results, func(i, j int) bool {
return strings.ToLower(results[i].Name) < strings.ToLower(results[j].Name)
})
return NewToolResultJSON(results)
}