Commit 25f5b8e
cmd/packages/main.go
@@ -0,0 +1,114 @@
+package main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "log"
+
+ "github.com/xlgmokha/mcp/pkg/packages"
+)
+
+func main() {
+ var showHelp bool
+ flag.BoolVar(&showHelp, "help", false, "Show help information")
+ flag.Parse()
+
+ if showHelp {
+ showHelpText()
+ return
+ }
+
+ server := packages.NewServer()
+ if err := server.Run(context.Background()); err != nil {
+ log.Fatalf("Server error: %v", err)
+ }
+}
+
+func showHelpText() {
+ fmt.Printf(`Package Manager MCP Server
+
+A Model Context Protocol server that provides package management tools for multiple
+ecosystems including Rust (Cargo) and macOS (Homebrew).
+
+USAGE:
+ mcp-packages [OPTIONS]
+
+OPTIONS:
+ --help Show this help message
+
+TOOLS PROVIDED:
+
+Cargo (Rust) Tools:
+ cargo_build Build Rust projects with optional flags
+ cargo_run Run Rust applications with arguments
+ cargo_test Execute test suites with filtering
+ cargo_add Add dependencies to Cargo.toml
+ cargo_update Update dependencies to latest versions
+ cargo_check Quick compile check without building
+ cargo_clippy Run Rust linter with suggestions
+
+Homebrew Tools:
+ brew_install Install packages or casks
+ brew_uninstall Remove packages or casks
+ brew_search Search for available packages
+ brew_update Update Homebrew itself
+ brew_upgrade Upgrade installed packages
+ brew_doctor Check system health
+ brew_list List installed packages
+
+Cross-Platform Tools:
+ check_vulnerabilities Scan for security vulnerabilities
+ outdated_packages Find packages needing updates
+ package_info Get detailed package information
+
+EXAMPLES:
+
+Cargo Operations:
+ # Build a Rust project in release mode
+ {"name": "cargo_build", "arguments": {"directory": ".", "release": true}}
+
+ # Run with arguments
+ {"name": "cargo_run", "arguments": {"args": ["--version"]}}
+
+ # Add a dependency
+ {"name": "cargo_add", "arguments": {"packages": ["serde", "tokio"]}}
+
+Homebrew Operations:
+ # Install a package
+ {"name": "brew_install", "arguments": {"packages": ["git"]}}
+
+ # Search for packages
+ {"name": "brew_search", "arguments": {"query": "nodejs"}}
+
+ # Check system health
+ {"name": "brew_doctor", "arguments": {}}
+
+Security & Maintenance:
+ # Check for vulnerabilities in current directory
+ {"name": "check_vulnerabilities", "arguments": {"directory": "."}}
+
+ # Find outdated packages
+ {"name": "outdated_packages", "arguments": {"package_manager": "cargo"}}
+
+INTEGRATION:
+Add to your Claude Code configuration (~/.claude.json):
+
+{
+ "mcpServers": {
+ "packages": {
+ "command": "mcp-packages"
+ }
+ }
+}
+
+ENVIRONMENT:
+The server auto-detects package managers based on project files:
+- Cargo.toml → Rust/Cargo tools
+- package.json → NPM tools
+- go.mod → Go modules
+- Homebrew → System-wide macOS packages
+
+For support or issues, see: https://github.com/xlgmokha/mcp
+`)
+}
pkg/packages/server.go
@@ -0,0 +1,987 @@
+package packages
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "sync"
+
+ "github.com/xlgmokha/mcp/pkg/mcp"
+)
+
+// Server represents the Package Manager MCP server
+type Server struct {
+ *mcp.Server
+ mu sync.RWMutex
+}
+
+// NewServer creates a new Package Manager MCP server
+func NewServer() *Server {
+ baseServer := mcp.NewServer("mcp-packages", "1.0.0")
+
+ server := &Server{
+ Server: baseServer,
+ }
+
+ // Register Cargo tools
+ server.RegisterTool("cargo_build", server.handleCargoBuild)
+ server.RegisterTool("cargo_run", server.handleCargoRun)
+ server.RegisterTool("cargo_test", server.handleCargoTest)
+ server.RegisterTool("cargo_add", server.handleCargoAdd)
+ server.RegisterTool("cargo_update", server.handleCargoUpdate)
+ server.RegisterTool("cargo_check", server.handleCargoCheck)
+ server.RegisterTool("cargo_clippy", server.handleCargoClippy)
+
+ // Register Homebrew tools
+ server.RegisterTool("brew_install", server.handleBrewInstall)
+ server.RegisterTool("brew_uninstall", server.handleBrewUninstall)
+ server.RegisterTool("brew_search", server.handleBrewSearch)
+ server.RegisterTool("brew_update", server.handleBrewUpdate)
+ server.RegisterTool("brew_upgrade", server.handleBrewUpgrade)
+ server.RegisterTool("brew_doctor", server.handleBrewDoctor)
+ server.RegisterTool("brew_list", server.handleBrewList)
+
+ // Register cross-platform tools
+ server.RegisterTool("check_vulnerabilities", server.handleCheckVulnerabilities)
+ server.RegisterTool("outdated_packages", server.handleOutdatedPackages)
+ server.RegisterTool("package_info", server.handlePackageInfo)
+
+ return server
+}
+
+// Cargo tool handlers
+
+func (s *Server) handleCargoBuild(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var args struct {
+ Directory string `json:"directory,omitempty"`
+ Release bool `json:"release,omitempty"`
+ Target string `json:"target,omitempty"`
+ Features string `json:"features,omitempty"`
+ }
+
+ argsBytes, _ := json.Marshal(req.Arguments)
+ if err := json.Unmarshal(argsBytes, &args); err != nil {
+ return mcp.CallToolResult{}, fmt.Errorf("invalid arguments: %w", err)
+ }
+
+ // Default to current directory
+ if args.Directory == "" {
+ args.Directory = "."
+ }
+
+ // Check if Cargo.toml exists
+ cargoToml := filepath.Join(args.Directory, "Cargo.toml")
+ if _, err := os.Stat(cargoToml); os.IsNotExist(err) {
+ return mcp.CallToolResult{}, fmt.Errorf("no Cargo.toml found in %s", args.Directory)
+ }
+
+ // Build cargo command
+ cmdArgs := []string{"build"}
+
+ if args.Release {
+ cmdArgs = append(cmdArgs, "--release")
+ }
+
+ if args.Target != "" {
+ cmdArgs = append(cmdArgs, "--target", args.Target)
+ }
+
+ if args.Features != "" {
+ cmdArgs = append(cmdArgs, "--features", args.Features)
+ }
+
+ cmd := exec.Command("cargo", cmdArgs...)
+ cmd.Dir = args.Directory
+
+ output, err := cmd.CombinedOutput()
+
+ result := fmt.Sprintf("Command: cargo %s\nDirectory: %s\nOutput:\n%s",
+ strings.Join(cmdArgs, " "), args.Directory, string(output))
+
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+func (s *Server) handleCargoRun(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var args struct {
+ Directory string `json:"directory,omitempty"`
+ Package string `json:"package,omitempty"`
+ Bin string `json:"bin,omitempty"`
+ Args []string `json:"args,omitempty"`
+ Release bool `json:"release,omitempty"`
+ }
+
+ argsBytes, _ := json.Marshal(req.Arguments)
+ if err := json.Unmarshal(argsBytes, &args); err != nil {
+ return mcp.CallToolResult{}, fmt.Errorf("invalid arguments: %w", err)
+ }
+
+ if args.Directory == "" {
+ args.Directory = "."
+ }
+
+ // Build cargo command
+ cmdArgs := []string{"run"}
+
+ if args.Release {
+ cmdArgs = append(cmdArgs, "--release")
+ }
+
+ if args.Package != "" {
+ cmdArgs = append(cmdArgs, "--package", args.Package)
+ }
+
+ if args.Bin != "" {
+ cmdArgs = append(cmdArgs, "--bin", args.Bin)
+ }
+
+ if len(args.Args) > 0 {
+ cmdArgs = append(cmdArgs, "--")
+ cmdArgs = append(cmdArgs, args.Args...)
+ }
+
+ cmd := exec.Command("cargo", cmdArgs...)
+ cmd.Dir = args.Directory
+
+ output, err := cmd.CombinedOutput()
+
+ result := fmt.Sprintf("Command: cargo %s\nDirectory: %s\nOutput:\n%s",
+ strings.Join(cmdArgs, " "), args.Directory, string(output))
+
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+func (s *Server) handleCargoTest(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var args struct {
+ Directory string `json:"directory,omitempty"`
+ Package string `json:"package,omitempty"`
+ Test string `json:"test,omitempty"`
+ Args []string `json:"args,omitempty"`
+ Release bool `json:"release,omitempty"`
+ }
+
+ argsBytes, _ := json.Marshal(req.Arguments)
+ if err := json.Unmarshal(argsBytes, &args); err != nil {
+ return mcp.CallToolResult{}, fmt.Errorf("invalid arguments: %w", err)
+ }
+
+ if args.Directory == "" {
+ args.Directory = "."
+ }
+
+ cmdArgs := []string{"test"}
+
+ if args.Release {
+ cmdArgs = append(cmdArgs, "--release")
+ }
+
+ if args.Package != "" {
+ cmdArgs = append(cmdArgs, "--package", args.Package)
+ }
+
+ if args.Test != "" {
+ cmdArgs = append(cmdArgs, args.Test)
+ }
+
+ if len(args.Args) > 0 {
+ cmdArgs = append(cmdArgs, "--")
+ cmdArgs = append(cmdArgs, args.Args...)
+ }
+
+ cmd := exec.Command("cargo", cmdArgs...)
+ cmd.Dir = args.Directory
+
+ output, err := cmd.CombinedOutput()
+
+ result := fmt.Sprintf("Command: cargo %s\nDirectory: %s\nOutput:\n%s",
+ strings.Join(cmdArgs, " "), args.Directory, string(output))
+
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+func (s *Server) handleCargoAdd(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var args struct {
+ Directory string `json:"directory,omitempty"`
+ Packages []string `json:"packages"`
+ Dev bool `json:"dev,omitempty"`
+ Build bool `json:"build,omitempty"`
+ Optional bool `json:"optional,omitempty"`
+ Features []string `json:"features,omitempty"`
+ NoDefaultFeatures bool `json:"no_default_features,omitempty"`
+ }
+
+ argsBytes, _ := json.Marshal(req.Arguments)
+ if err := json.Unmarshal(argsBytes, &args); err != nil {
+ return mcp.CallToolResult{}, fmt.Errorf("invalid arguments: %w", err)
+ }
+
+ if len(args.Packages) == 0 {
+ return mcp.CallToolResult{}, fmt.Errorf("packages are required")
+ }
+
+ if args.Directory == "" {
+ args.Directory = "."
+ }
+
+ cmdArgs := []string{"add"}
+ cmdArgs = append(cmdArgs, args.Packages...)
+
+ if args.Dev {
+ cmdArgs = append(cmdArgs, "--dev")
+ }
+
+ if args.Build {
+ cmdArgs = append(cmdArgs, "--build")
+ }
+
+ if args.Optional {
+ cmdArgs = append(cmdArgs, "--optional")
+ }
+
+ if len(args.Features) > 0 {
+ cmdArgs = append(cmdArgs, "--features", strings.Join(args.Features, ","))
+ }
+
+ if args.NoDefaultFeatures {
+ cmdArgs = append(cmdArgs, "--no-default-features")
+ }
+
+ cmd := exec.Command("cargo", cmdArgs...)
+ cmd.Dir = args.Directory
+
+ output, err := cmd.CombinedOutput()
+
+ result := fmt.Sprintf("Command: cargo %s\nDirectory: %s\nOutput:\n%s",
+ strings.Join(cmdArgs, " "), args.Directory, string(output))
+
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+func (s *Server) handleCargoUpdate(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var args struct {
+ Directory string `json:"directory,omitempty"`
+ Packages []string `json:"packages,omitempty"`
+ Aggressive bool `json:"aggressive,omitempty"`
+ Precise string `json:"precise,omitempty"`
+ }
+
+ argsBytes, _ := json.Marshal(req.Arguments)
+ if err := json.Unmarshal(argsBytes, &args); err != nil {
+ return mcp.CallToolResult{}, fmt.Errorf("invalid arguments: %w", err)
+ }
+
+ if args.Directory == "" {
+ args.Directory = "."
+ }
+
+ cmdArgs := []string{"update"}
+
+ if len(args.Packages) > 0 {
+ cmdArgs = append(cmdArgs, args.Packages...)
+ }
+
+ if args.Aggressive {
+ cmdArgs = append(cmdArgs, "--aggressive")
+ }
+
+ if args.Precise != "" {
+ cmdArgs = append(cmdArgs, "--precise", args.Precise)
+ }
+
+ cmd := exec.Command("cargo", cmdArgs...)
+ cmd.Dir = args.Directory
+
+ output, err := cmd.CombinedOutput()
+
+ result := fmt.Sprintf("Command: cargo %s\nDirectory: %s\nOutput:\n%s",
+ strings.Join(cmdArgs, " "), args.Directory, string(output))
+
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+func (s *Server) handleCargoCheck(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var args struct {
+ Directory string `json:"directory,omitempty"`
+ Package string `json:"package,omitempty"`
+ Release bool `json:"release,omitempty"`
+ AllTargets bool `json:"all_targets,omitempty"`
+ }
+
+ argsBytes, _ := json.Marshal(req.Arguments)
+ if err := json.Unmarshal(argsBytes, &args); err != nil {
+ return mcp.CallToolResult{}, fmt.Errorf("invalid arguments: %w", err)
+ }
+
+ if args.Directory == "" {
+ args.Directory = "."
+ }
+
+ cmdArgs := []string{"check"}
+
+ if args.Release {
+ cmdArgs = append(cmdArgs, "--release")
+ }
+
+ if args.Package != "" {
+ cmdArgs = append(cmdArgs, "--package", args.Package)
+ }
+
+ if args.AllTargets {
+ cmdArgs = append(cmdArgs, "--all-targets")
+ }
+
+ cmd := exec.Command("cargo", cmdArgs...)
+ cmd.Dir = args.Directory
+
+ output, err := cmd.CombinedOutput()
+
+ result := fmt.Sprintf("Command: cargo %s\nDirectory: %s\nOutput:\n%s",
+ strings.Join(cmdArgs, " "), args.Directory, string(output))
+
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+func (s *Server) handleCargoClippy(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var args struct {
+ Directory string `json:"directory,omitempty"`
+ Package string `json:"package,omitempty"`
+ AllTargets bool `json:"all_targets,omitempty"`
+ Fix bool `json:"fix,omitempty"`
+ }
+
+ argsBytes, _ := json.Marshal(req.Arguments)
+ if err := json.Unmarshal(argsBytes, &args); err != nil {
+ return mcp.CallToolResult{}, fmt.Errorf("invalid arguments: %w", err)
+ }
+
+ if args.Directory == "" {
+ args.Directory = "."
+ }
+
+ cmdArgs := []string{"clippy"}
+
+ if args.Package != "" {
+ cmdArgs = append(cmdArgs, "--package", args.Package)
+ }
+
+ if args.AllTargets {
+ cmdArgs = append(cmdArgs, "--all-targets")
+ }
+
+ if args.Fix {
+ cmdArgs = append(cmdArgs, "--fix")
+ }
+
+ cmd := exec.Command("cargo", cmdArgs...)
+ cmd.Dir = args.Directory
+
+ output, err := cmd.CombinedOutput()
+
+ result := fmt.Sprintf("Command: cargo %s\nDirectory: %s\nOutput:\n%s",
+ strings.Join(cmdArgs, " "), args.Directory, string(output))
+
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+// Homebrew tool handlers
+
+func (s *Server) handleBrewInstall(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var args struct {
+ Packages []string `json:"packages"`
+ Cask bool `json:"cask,omitempty"`
+ Force bool `json:"force,omitempty"`
+ }
+
+ argsBytes, _ := json.Marshal(req.Arguments)
+ if err := json.Unmarshal(argsBytes, &args); err != nil {
+ return mcp.CallToolResult{}, fmt.Errorf("invalid arguments: %w", err)
+ }
+
+ if len(args.Packages) == 0 {
+ return mcp.CallToolResult{}, fmt.Errorf("packages are required")
+ }
+
+ cmdArgs := []string{"install"}
+
+ if args.Cask {
+ cmdArgs = append(cmdArgs, "--cask")
+ }
+
+ if args.Force {
+ cmdArgs = append(cmdArgs, "--force")
+ }
+
+ cmdArgs = append(cmdArgs, args.Packages...)
+
+ cmd := exec.Command("brew", cmdArgs...)
+ output, err := cmd.CombinedOutput()
+
+ result := fmt.Sprintf("Command: brew %s\nOutput:\n%s",
+ strings.Join(cmdArgs, " "), string(output))
+
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+func (s *Server) handleBrewUninstall(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var args struct {
+ Packages []string `json:"packages"`
+ Cask bool `json:"cask,omitempty"`
+ Force bool `json:"force,omitempty"`
+ }
+
+ argsBytes, _ := json.Marshal(req.Arguments)
+ if err := json.Unmarshal(argsBytes, &args); err != nil {
+ return mcp.CallToolResult{}, fmt.Errorf("invalid arguments: %w", err)
+ }
+
+ if len(args.Packages) == 0 {
+ return mcp.CallToolResult{}, fmt.Errorf("packages are required")
+ }
+
+ cmdArgs := []string{"uninstall"}
+
+ if args.Cask {
+ cmdArgs = append(cmdArgs, "--cask")
+ }
+
+ if args.Force {
+ cmdArgs = append(cmdArgs, "--force")
+ }
+
+ cmdArgs = append(cmdArgs, args.Packages...)
+
+ cmd := exec.Command("brew", cmdArgs...)
+ output, err := cmd.CombinedOutput()
+
+ result := fmt.Sprintf("Command: brew %s\nOutput:\n%s",
+ strings.Join(cmdArgs, " "), string(output))
+
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+func (s *Server) handleBrewSearch(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var args struct {
+ Query string `json:"query"`
+ Cask bool `json:"cask,omitempty"`
+ }
+
+ argsBytes, _ := json.Marshal(req.Arguments)
+ if err := json.Unmarshal(argsBytes, &args); err != nil {
+ return mcp.CallToolResult{}, fmt.Errorf("invalid arguments: %w", err)
+ }
+
+ if args.Query == "" {
+ return mcp.CallToolResult{}, fmt.Errorf("query is required")
+ }
+
+ cmdArgs := []string{"search"}
+
+ if args.Cask {
+ cmdArgs = append(cmdArgs, "--cask")
+ }
+
+ cmdArgs = append(cmdArgs, args.Query)
+
+ cmd := exec.Command("brew", cmdArgs...)
+ output, err := cmd.CombinedOutput()
+
+ result := fmt.Sprintf("Command: brew %s\nOutput:\n%s",
+ strings.Join(cmdArgs, " "), string(output))
+
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+func (s *Server) handleBrewUpdate(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ cmd := exec.Command("brew", "update")
+ output, err := cmd.CombinedOutput()
+
+ result := fmt.Sprintf("Command: brew update\nOutput:\n%s", string(output))
+
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+func (s *Server) handleBrewUpgrade(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var args struct {
+ Packages []string `json:"packages,omitempty"`
+ Cask bool `json:"cask,omitempty"`
+ }
+
+ argsBytes, _ := json.Marshal(req.Arguments)
+ if err := json.Unmarshal(argsBytes, &args); err != nil {
+ return mcp.CallToolResult{}, fmt.Errorf("invalid arguments: %w", err)
+ }
+
+ cmdArgs := []string{"upgrade"}
+
+ if args.Cask {
+ cmdArgs = append(cmdArgs, "--cask")
+ }
+
+ if len(args.Packages) > 0 {
+ cmdArgs = append(cmdArgs, args.Packages...)
+ }
+
+ cmd := exec.Command("brew", cmdArgs...)
+ output, err := cmd.CombinedOutput()
+
+ result := fmt.Sprintf("Command: brew %s\nOutput:\n%s",
+ strings.Join(cmdArgs, " "), string(output))
+
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+func (s *Server) handleBrewDoctor(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ cmd := exec.Command("brew", "doctor")
+ output, err := cmd.CombinedOutput()
+
+ result := fmt.Sprintf("Command: brew doctor\nOutput:\n%s", string(output))
+
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+func (s *Server) handleBrewList(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var args struct {
+ Cask bool `json:"cask,omitempty"`
+ Versions bool `json:"versions,omitempty"`
+ }
+
+ argsBytes, _ := json.Marshal(req.Arguments)
+ if err := json.Unmarshal(argsBytes, &args); err != nil {
+ return mcp.CallToolResult{}, fmt.Errorf("invalid arguments: %w", err)
+ }
+
+ cmdArgs := []string{"list"}
+
+ if args.Cask {
+ cmdArgs = append(cmdArgs, "--cask")
+ }
+
+ if args.Versions {
+ cmdArgs = append(cmdArgs, "--versions")
+ }
+
+ cmd := exec.Command("brew", cmdArgs...)
+ output, err := cmd.CombinedOutput()
+
+ result := fmt.Sprintf("Command: brew %s\nOutput:\n%s",
+ strings.Join(cmdArgs, " "), string(output))
+
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+// Cross-platform tool handlers
+
+func (s *Server) handleCheckVulnerabilities(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var args struct {
+ Directory string `json:"directory,omitempty"`
+ PackageManager string `json:"package_manager,omitempty"`
+ }
+
+ argsBytes, _ := json.Marshal(req.Arguments)
+ if err := json.Unmarshal(argsBytes, &args); err != nil {
+ return mcp.CallToolResult{}, fmt.Errorf("invalid arguments: %w", err)
+ }
+
+ if args.Directory == "" {
+ args.Directory = "."
+ }
+
+ var result string
+
+ // Auto-detect package manager if not specified
+ if args.PackageManager == "" {
+ if _, err := os.Stat(filepath.Join(args.Directory, "Cargo.toml")); err == nil {
+ args.PackageManager = "cargo"
+ } else if _, err := os.Stat(filepath.Join(args.Directory, "package.json")); err == nil {
+ args.PackageManager = "npm"
+ } else if _, err := os.Stat(filepath.Join(args.Directory, "go.mod")); err == nil {
+ args.PackageManager = "go"
+ }
+ }
+
+ switch args.PackageManager {
+ case "cargo":
+ cmd := exec.Command("cargo", "audit")
+ cmd.Dir = args.Directory
+ output, err := cmd.CombinedOutput()
+ result = fmt.Sprintf("Cargo security audit:\n%s", string(output))
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+ case "npm":
+ cmd := exec.Command("npm", "audit")
+ cmd.Dir = args.Directory
+ output, err := cmd.CombinedOutput()
+ result = fmt.Sprintf("NPM security audit:\n%s", string(output))
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+ case "go":
+ cmd := exec.Command("go", "list", "-json", "-m", "all")
+ cmd.Dir = args.Directory
+ output, err := cmd.CombinedOutput()
+ result = fmt.Sprintf("Go module list (manual vulnerability check needed):\n%s", string(output))
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+ default:
+ result = "Unable to detect package manager. Please specify package_manager: 'cargo', 'npm', or 'go'"
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+func (s *Server) handleOutdatedPackages(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var args struct {
+ Directory string `json:"directory,omitempty"`
+ PackageManager string `json:"package_manager,omitempty"`
+ }
+
+ argsBytes, _ := json.Marshal(req.Arguments)
+ if err := json.Unmarshal(argsBytes, &args); err != nil {
+ return mcp.CallToolResult{}, fmt.Errorf("invalid arguments: %w", err)
+ }
+
+ if args.Directory == "" {
+ args.Directory = "."
+ }
+
+ var result string
+
+ // Auto-detect package manager if not specified
+ if args.PackageManager == "" {
+ if _, err := os.Stat(filepath.Join(args.Directory, "Cargo.toml")); err == nil {
+ args.PackageManager = "cargo"
+ } else if _, err := os.Stat(filepath.Join(args.Directory, "package.json")); err == nil {
+ args.PackageManager = "npm"
+ } else if _, err := os.Stat(filepath.Join(args.Directory, "go.mod")); err == nil {
+ args.PackageManager = "go"
+ }
+ }
+
+ switch args.PackageManager {
+ case "cargo":
+ cmd := exec.Command("cargo", "outdated")
+ cmd.Dir = args.Directory
+ output, err := cmd.CombinedOutput()
+ result = fmt.Sprintf("Cargo outdated packages:\n%s", string(output))
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v (try: cargo install cargo-outdated)", err)
+ }
+ case "npm":
+ cmd := exec.Command("npm", "outdated")
+ cmd.Dir = args.Directory
+ output, err := cmd.CombinedOutput()
+ result = fmt.Sprintf("NPM outdated packages:\n%s", string(output))
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+ case "go":
+ cmd := exec.Command("go", "list", "-u", "-m", "all")
+ cmd.Dir = args.Directory
+ output, err := cmd.CombinedOutput()
+ result = fmt.Sprintf("Go modules with available updates:\n%s", string(output))
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+ case "brew":
+ cmd := exec.Command("brew", "outdated")
+ output, err := cmd.CombinedOutput()
+ result = fmt.Sprintf("Homebrew outdated packages:\n%s", string(output))
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+ default:
+ result = "Unable to detect package manager. Please specify package_manager: 'cargo', 'npm', 'go', or 'brew'"
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
+
+func (s *Server) handlePackageInfo(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ var args struct {
+ Package string `json:"package"`
+ PackageManager string `json:"package_manager,omitempty"`
+ Directory string `json:"directory,omitempty"`
+ }
+
+ argsBytes, _ := json.Marshal(req.Arguments)
+ if err := json.Unmarshal(argsBytes, &args); err != nil {
+ return mcp.CallToolResult{}, fmt.Errorf("invalid arguments: %w", err)
+ }
+
+ if args.Package == "" {
+ return mcp.CallToolResult{}, fmt.Errorf("package name is required")
+ }
+
+ if args.Directory == "" {
+ args.Directory = "."
+ }
+
+ var result string
+
+ // Auto-detect package manager if not specified
+ if args.PackageManager == "" {
+ if _, err := os.Stat(filepath.Join(args.Directory, "Cargo.toml")); err == nil {
+ args.PackageManager = "cargo"
+ } else if _, err := os.Stat(filepath.Join(args.Directory, "package.json")); err == nil {
+ args.PackageManager = "npm"
+ }
+ }
+
+ switch args.PackageManager {
+ case "cargo":
+ cmd := exec.Command("cargo", "search", args.Package, "--limit", "1")
+ output, err := cmd.CombinedOutput()
+ result = fmt.Sprintf("Cargo package info for '%s':\n%s", args.Package, string(output))
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+ case "npm":
+ cmd := exec.Command("npm", "info", args.Package)
+ output, err := cmd.CombinedOutput()
+ result = fmt.Sprintf("NPM package info for '%s':\n%s", args.Package, string(output))
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+ case "brew":
+ cmd := exec.Command("brew", "info", args.Package)
+ output, err := cmd.CombinedOutput()
+ result = fmt.Sprintf("Homebrew package info for '%s':\n%s", args.Package, string(output))
+ if err != nil {
+ result += fmt.Sprintf("\nError: %v", err)
+ }
+ default:
+ result = "Unable to detect package manager. Please specify package_manager: 'cargo', 'npm', or 'brew'"
+ }
+
+ return mcp.CallToolResult{
+ Content: []mcp.Content{
+ mcp.TextContent{
+ Type: "text",
+ Text: result,
+ },
+ },
+ }, nil
+}
Makefile
@@ -11,7 +11,7 @@ BINDIR = bin
INSTALLDIR = /usr/local/bin
# Server binaries
-SERVERS = git filesystem fetch memory sequential-thinking time maildir signal gitlab imap bash
+SERVERS = git filesystem fetch memory sequential-thinking time maildir signal gitlab imap bash packages
BINARIES = $(addprefix $(BINDIR)/mcp-,$(SERVERS))
# Build flags