Commit 25f5b8e

mo khan <mo@mokhan.ca>
2025-07-04 06:05:59
add package manager mcp server
1 parent 9ccafed
Changed files (3)
cmd
packages
pkg
packages
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