Commit ce09b14

mo khan <mo@mokhan.ca>
2025-08-18 20:40:27
refactor: slim down the bash mcp server interface
1 parent e90b897
Changed files (5)
cmd/bash/main.go
@@ -1,140 +1,69 @@
 package main
 
 import (
-	"context"
-	"flag"
-	"fmt"
-	"log"
+  "context"
+  "flag"
+  "fmt"
+  "log"
 
-	"github.com/xlgmokha/mcp/pkg/bash"
+  "github.com/xlgmokha/mcp/pkg/bash"
 )
 
-func main() {
-	// Define command line flags
-	var (
-		defaultTimeout = flag.Int("default-timeout", 30, "Default command timeout in seconds")
-		maxTimeout     = flag.Int("max-timeout", 300, "Maximum command timeout in seconds")
-		historySize    = flag.Int("history-size", 100, "Command history size")
-		workingDir     = flag.String("working-dir", "", "Default working directory (default: current)")
-		help           = flag.Bool("help", false, "Show help information")
-		version        = flag.Bool("version", false, "Show version information")
-	)
-
-	flag.Parse()
-
-	// Show help
-	if *help {
-		printHelp()
-		return
-	}
-
-	// Show version
-	if *version {
-		fmt.Println("Bash MCP Server v1.0.0")
-		return
-	}
-
-	// Create configuration from command line flags and environment
-	config := bash.ConfigFromEnv()
-
-	// Override with command line flags if provided
-	if *defaultTimeout != 30 {
-		config.DefaultTimeout = *defaultTimeout
-	}
-	if *maxTimeout != 300 {
-		config.MaxTimeout = *maxTimeout
-	}
-	if *historySize != 100 {
-		config.HistorySize = *historySize
-	}
-	if *workingDir != "" {
-		config.WorkingDir = *workingDir
-	}
-
-	// Validate configuration
-	if config.DefaultTimeout <= 0 {
-		log.Fatal("Default timeout must be positive")
-	}
-	if config.MaxTimeout <= 0 {
-		log.Fatal("Maximum timeout must be positive")
-	}
-	if config.DefaultTimeout > config.MaxTimeout {
-		log.Fatal("Default timeout cannot exceed maximum timeout")
-	}
-	if config.HistorySize <= 0 {
-		log.Fatal("History size must be positive")
-	}
-
-	// Create and start the server
-	server, err := bash.New(config)
-	if err != nil {
-		log.Fatalf("Failed to create bash server: %v", err)
-	}
-
-	// Run the MCP server
-	ctx := context.Background()
-	if err := server.Run(ctx); err != nil {
-		log.Fatalf("Server error: %v", err)
-	}
+func printHelp() {
+  fmt.Printf(`Bash MCP Server
+
+DESCRIPTION:
+    A Model Context Protocol server that provides shell command execution capabilities.
+    Enables direct execution of bash commands with streaming output.
+
+USAGE:
+    mcp-bash [directory]
+
+ARGUMENTS:
+    directory    Working directory for command execution (default: current directory)
+
+OPTIONS:
+    --help       Show this help message
+
+EXAMPLE USAGE:
+    # Use current directory
+    mcp-bash
+
+    # Use specific directory
+    mcp-bash /path/to/project
+
+    # Execute a command
+    echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "exec", "arguments": {"command": "ls -la"}}}' | mcp-bash
+
+MCP CAPABILITIES:
+    - Tools: exec (execute shell commands with streaming output)
+    - Resources: bash builtins and coreutils discovery
+    - Protocol: JSON-RPC 2.0 over stdio
+
+For detailed documentation, see: cmd/bash/README.md
+`)
 }
 
-func printHelp() {
-	fmt.Println("Bash MCP Server")
-	fmt.Println("================")
-	fmt.Println()
-	fmt.Println("A Model Context Protocol server that provides AI coding agents with direct")
-	fmt.Println("shell command execution capabilities. This server enables agents to perform")
-	fmt.Println("any system operation that doesn't require sudo privileges.")
-	fmt.Println()
-	fmt.Println("USAGE:")
-	fmt.Println("  mcp-bash [options]")
-	fmt.Println()
-	fmt.Println("OPTIONS:")
-	fmt.Println("  --default-timeout int     Default command timeout in seconds (default: 30)")
-	fmt.Println("  --max-timeout int         Maximum command timeout in seconds (default: 300)")
-	fmt.Println("  --history-size int        Command history size (default: 100)")
-	fmt.Println("  --working-dir string      Default working directory (default: current)")
-	fmt.Println("  --help                    Show this help message")
-	fmt.Println("  --version                 Show version information")
-	fmt.Println()
-	fmt.Println("ENVIRONMENT VARIABLES:")
-	fmt.Println("  BASH_MCP_DEFAULT_TIMEOUT  Default command timeout")
-	fmt.Println("  BASH_MCP_MAX_TIMEOUT      Maximum allowed timeout")
-	fmt.Println("  BASH_MCP_MAX_HISTORY      Command history size")
-	fmt.Println("  BASH_MCP_WORKING_DIR      Default working directory")
-	fmt.Println()
-	fmt.Println("AVAILABLE TOOLS:")
-	fmt.Println("  bash_exec                 Execute a shell command and return output")
-	fmt.Println("  bash_exec_stream          Execute command with real-time output streaming")
-	fmt.Println("  man_page                  Get manual page for a command")
-	fmt.Println("  which_command             Find the location of a command")
-	fmt.Println("  command_help              Get help text for a command (--help flag)")
-	fmt.Println("  get_env                   Get environment variable value")
-	fmt.Println("  get_working_dir           Get the current working directory")
-	fmt.Println("  set_working_dir           Set working directory for future commands")
-	fmt.Println("  system_info               Get basic system information")
-	fmt.Println("  process_info              Get information about running processes")
-	fmt.Println()
-	fmt.Println("AVAILABLE RESOURCES:")
-	fmt.Println("  bash://system/info        Live system information and environment state")
-	fmt.Println("  bash://history/recent     Recent command execution history")
-	fmt.Println("  bash://env/all           Complete environment variables")
-	fmt.Println()
-	fmt.Println("EXAMPLE USAGE:")
-	fmt.Println("  # Execute a simple command:")
-	fmt.Println(`  echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "bash_exec", "arguments": {"command": "ls -la"}}}' | mcp-bash`)
-	fmt.Println()
-	fmt.Println("  # Get system information:")
-	fmt.Println(`  echo '{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "system_info", "arguments": {}}}' | mcp-bash`)
-	fmt.Println()
-	fmt.Println("  # Set working directory and run command:")
-	fmt.Println(`  echo '{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "set_working_dir", "arguments": {"directory": "/tmp"}}}' | mcp-bash`)
-	fmt.Println()
-	fmt.Println("SECURITY CONSIDERATIONS:")
-	fmt.Println("  This server provides unrestricted shell access within user permissions.")
-	fmt.Println("  It does not filter commands or provide sandboxing. Use with caution in")
-	fmt.Println("  production environments and ensure proper system-level security measures.")
-	fmt.Println()
-	fmt.Println("For more information about the Model Context Protocol, visit:")
-	fmt.Println("https://github.com/anthropics/mcp")
+func main() {
+  var help = flag.Bool("help", false, "Show help message")
+  flag.Parse()
+
+  if *help {
+    printHelp()
+    return
+  }
+
+  var workingDir string
+  if len(flag.Args()) > 0 {
+    workingDir = flag.Arg(0)
+  } else {
+    workingDir = "."
+  }
+
+  server := bash.New(workingDir)
+
+  ctx := context.Background()
+  if err := server.Run(ctx); err != nil {
+    log.Fatalf("Server error: %v", err)
+  }
 }
\ No newline at end of file
cmd/fetch/main.go
@@ -29,10 +29,6 @@ EXAMPLE USAGE:
     # Test with MCP protocol
     echo '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "fetch", "arguments": {"url": "https://example.com"}}}' | mcp-fetch
 
-ADDING TO CLAUDE CODE:
-    # Add to Claude Code (no configuration needed)
-    claude mcp add mcp-fetch -- /usr/local/bin/mcp-fetch
-
 MCP CAPABILITIES:
     - Tools: fetch (web content retrieval with HTML processing)
     - Features: goquery HTML parsing, html-to-markdown conversion
pkg/bash/handlers.go
@@ -1,393 +0,0 @@
-package bash
-
-import (
-	"encoding/json"
-	"fmt"
-	"os"
-	"runtime"
-	"strings"
-
-	"github.com/xlgmokha/mcp/pkg/mcp"
-)
-
-// Core Execution Tools
-
-// HandleBashExec handles the bash_exec tool
-func (bash *BashOperations) HandleBashExec(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-	var options ExecutionOptions
-
-	// Parse command (required)
-	command, ok := req.Arguments["command"].(string)
-	if !ok || command == "" {
-		return mcp.NewToolError("command parameter is required"), nil
-	}
-	options.Command = command
-
-	// Parse optional parameters
-	if workingDir, ok := req.Arguments["working_dir"].(string); ok {
-		options.WorkingDir = workingDir
-	}
-
-	if timeoutFloat, ok := req.Arguments["timeout"].(float64); ok {
-		options.Timeout = int(timeoutFloat)
-	}
-
-	if captureStderr, ok := req.Arguments["capture_stderr"].(bool); ok {
-		options.CaptureStderr = captureStderr
-	} else {
-		options.CaptureStderr = true // default to true
-	}
-
-	// Parse environment variables
-	if envInterface, ok := req.Arguments["env"]; ok {
-		if envMap, ok := envInterface.(map[string]interface{}); ok {
-			options.Env = make(map[string]string)
-			for key, value := range envMap {
-				if strValue, ok := value.(string); ok {
-					options.Env[key] = strValue
-				}
-			}
-		}
-	}
-
-	// Execute command
-	result, err := bash.executeCommand(options)
-	if err != nil {
-		return mcp.NewToolError(fmt.Sprintf("Failed to execute command: %v", err)), nil
-	}
-
-	// Return result as JSON
-	return mcp.CallToolResult{
-		Content: []mcp.Content{
-			mcp.TextContent{
-				Type: "text",
-				Text: toJSONString(result),
-			},
-		},
-	}, nil
-}
-
-// HandleBashExecStream handles the bash_exec_stream tool
-func (bash *BashOperations) HandleBashExecStream(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-	// For now, this is the same as regular execution
-	// TODO: Implement actual streaming in future version
-	return bash.HandleBashExec(req)
-}
-
-// Documentation Tools
-
-// HandleManPage handles the man_page tool
-func (bash *BashOperations) HandleManPage(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-	command, ok := req.Arguments["command"].(string)
-	if !ok || command == "" {
-		return mcp.NewToolError("command parameter is required"), nil
-	}
-
-	// Build man command
-	manCmd := fmt.Sprintf("man %s", command)
-	if section, ok := req.Arguments["section"].(string); ok && section != "" {
-		manCmd = fmt.Sprintf("man %s %s", section, command)
-	}
-
-	options := ExecutionOptions{
-		Command:       manCmd,
-		CaptureStderr: true,
-	}
-
-	result, err := bash.executeCommand(options)
-	if err != nil {
-		return mcp.NewToolError(fmt.Sprintf("Failed to get man page: %v", err)), nil
-	}
-
-	return mcp.CallToolResult{
-		Content: []mcp.Content{
-			mcp.TextContent{
-				Type: "text",
-				Text: result.Stdout,
-			},
-		},
-	}, nil
-}
-
-// HandleWhichCommand handles the which_command tool
-func (bash *BashOperations) HandleWhichCommand(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-	command, ok := req.Arguments["command"].(string)
-	if !ok || command == "" {
-		return mcp.NewToolError("command parameter is required"), nil
-	}
-
-	var whichCmd string
-	if runtime.GOOS == "windows" {
-		whichCmd = fmt.Sprintf("where %s", command)
-	} else {
-		whichCmd = fmt.Sprintf("which %s", command)
-	}
-
-	options := ExecutionOptions{
-		Command:       whichCmd,
-		CaptureStderr: true,
-	}
-
-	result, err := bash.executeCommand(options)
-	if err != nil {
-		return mcp.NewToolError(fmt.Sprintf("Failed to locate command: %v", err)), nil
-	}
-
-	return mcp.CallToolResult{
-		Content: []mcp.Content{
-			mcp.TextContent{
-				Type: "text",
-				Text: strings.TrimSpace(result.Stdout),
-			},
-		},
-	}, nil
-}
-
-// HandleCommandHelp handles the command_help tool
-func (bash *BashOperations) HandleCommandHelp(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-	command, ok := req.Arguments["command"].(string)
-	if !ok || command == "" {
-		return mcp.NewToolError("command parameter is required"), nil
-	}
-
-	helpCmd := fmt.Sprintf("%s --help", command)
-
-	options := ExecutionOptions{
-		Command:       helpCmd,
-		CaptureStderr: true,
-	}
-
-	result, err := bash.executeCommand(options)
-	if err != nil {
-		return mcp.NewToolError(fmt.Sprintf("Failed to get command help: %v", err)), nil
-	}
-
-	// Some commands output help to stderr
-	output := result.Stdout
-	if output == "" && result.Stderr != "" {
-		output = result.Stderr
-	}
-
-	return mcp.CallToolResult{
-		Content: []mcp.Content{
-			mcp.TextContent{
-				Type: "text",
-				Text: output,
-			},
-		},
-	}, nil
-}
-
-// Environment Management Tools
-
-// HandleGetEnv handles the get_env tool
-func (bash *BashOperations) HandleGetEnv(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-	// Check if requesting all environment variables
-	if all, ok := req.Arguments["all"].(bool); ok && all {
-		envVars := make(map[string]string)
-		for _, env := range os.Environ() {
-			parts := strings.SplitN(env, "=", 2)
-			if len(parts) == 2 {
-				envVars[parts[0]] = parts[1]
-			}
-		}
-
-		return mcp.CallToolResult{
-			Content: []mcp.Content{
-				mcp.TextContent{
-					Type: "text",
-					Text: toJSONString(envVars),
-				},
-			},
-		}, nil
-	}
-
-	// Get specific variable
-	variable, ok := req.Arguments["variable"].(string)
-	if !ok || variable == "" {
-		return mcp.NewToolError("variable parameter is required when all=false"), nil
-	}
-
-	value := os.Getenv(variable)
-	result := map[string]string{
-		variable: value,
-	}
-
-	return mcp.CallToolResult{
-		Content: []mcp.Content{
-			mcp.TextContent{
-				Type: "text",
-				Text: toJSONString(result),
-			},
-		},
-	}, nil
-}
-
-// HandleGetWorkingDir handles the get_working_dir tool
-func (bash *BashOperations) HandleGetWorkingDir(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-	workingDir := bash.getWorkingDir()
-
-	result := map[string]string{
-		"working_directory": workingDir,
-	}
-
-	return mcp.CallToolResult{
-		Content: []mcp.Content{
-			mcp.TextContent{
-				Type: "text",
-				Text: toJSONString(result),
-			},
-		},
-	}, nil
-}
-
-// HandleSetWorkingDir handles the set_working_dir tool
-func (bash *BashOperations) HandleSetWorkingDir(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-	directory, ok := req.Arguments["directory"].(string)
-	if !ok || directory == "" {
-		return mcp.NewToolError("directory parameter is required"), nil
-	}
-
-	if err := bash.setWorkingDir(directory); err != nil {
-		return mcp.NewToolError(fmt.Sprintf("Failed to set working directory: %v", err)), nil
-	}
-
-	result := map[string]string{
-		"working_directory": bash.getWorkingDir(),
-		"message":          "Working directory updated successfully",
-	}
-
-	return mcp.CallToolResult{
-		Content: []mcp.Content{
-			mcp.TextContent{
-				Type: "text",
-				Text: toJSONString(result),
-			},
-		},
-	}, nil
-}
-
-// System Information Tools
-
-// HandleSystemInfo handles the system_info tool
-func (bash *BashOperations) HandleSystemInfo(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-	sysInfo, err := bash.getSystemInfo()
-	if err != nil {
-		return mcp.NewToolError(fmt.Sprintf("Failed to get system information: %v", err)), nil
-	}
-
-	return mcp.CallToolResult{
-		Content: []mcp.Content{
-			mcp.TextContent{
-				Type: "text",
-				Text: toJSONString(sysInfo),
-			},
-		},
-	}, nil
-}
-
-// HandleProcessInfo handles the process_info tool
-func (bash *BashOperations) HandleProcessInfo(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-	// Build ps command
-	format := "aux" // default format
-	if f, ok := req.Arguments["format"].(string); ok && f != "" {
-		format = f
-	}
-
-	var psCmd string
-	if runtime.GOOS == "windows" {
-		psCmd = "tasklist"
-	} else {
-		psCmd = fmt.Sprintf("ps %s", format)
-	}
-
-	// Add filter if specified
-	if filter, ok := req.Arguments["filter"].(string); ok && filter != "" {
-		if runtime.GOOS == "windows" {
-			psCmd = fmt.Sprintf("%s | findstr %s", psCmd, filter)
-		} else {
-			psCmd = fmt.Sprintf("%s | grep %s", psCmd, filter)
-		}
-	}
-
-	options := ExecutionOptions{
-		Command:       psCmd,
-		CaptureStderr: true,
-	}
-
-	result, err := bash.executeCommand(options)
-	if err != nil {
-		return mcp.NewToolError(fmt.Sprintf("Failed to get process information: %v", err)), nil
-	}
-
-	return mcp.CallToolResult{
-		Content: []mcp.Content{
-			mcp.TextContent{
-				Type: "text",
-				Text: result.Stdout,
-			},
-		},
-	}, nil
-}
-
-// Resource Handlers
-
-// HandleSystemResource handles the system information resource
-func (bash *BashOperations) HandleSystemResource(req mcp.ReadResourceRequest) (mcp.ReadResourceResult, error) {
-	sysInfo, err := bash.getSystemInfo()
-	if err != nil {
-		return mcp.ReadResourceResult{}, fmt.Errorf("failed to get system information: %w", err)
-	}
-
-	return mcp.ReadResourceResult{
-		Contents: []mcp.Content{
-			mcp.TextContent{
-				Type: "text",
-				Text: toJSONString(sysInfo),
-			},
-		},
-	}, nil
-}
-
-// HandleHistoryResource handles the command history resource
-func (bash *BashOperations) HandleHistoryResource(req mcp.ReadResourceRequest) (mcp.ReadResourceResult, error) {
-	history := bash.getCommandHistory()
-
-	return mcp.ReadResourceResult{
-		Contents: []mcp.Content{
-			mcp.TextContent{
-				Type: "text",
-				Text: toJSONString(history),
-			},
-		},
-	}, nil
-}
-
-// HandleEnvResource handles the environment variables resource
-func (bash *BashOperations) HandleEnvResource(req mcp.ReadResourceRequest) (mcp.ReadResourceResult, error) {
-	envVars := make(map[string]string)
-	for _, env := range os.Environ() {
-		parts := strings.SplitN(env, "=", 2)
-		if len(parts) == 2 {
-			envVars[parts[0]] = parts[1]
-		}
-	}
-
-	return mcp.ReadResourceResult{
-		Contents: []mcp.Content{
-			mcp.TextContent{
-				Type: "text",
-				Text: toJSONString(envVars),
-			},
-		},
-	}, nil
-}
-
-// Helper function to convert data to JSON string
-func toJSONString(data interface{}) string {
-	jsonData, err := json.MarshalIndent(data, "", "  ")
-	if err != nil {
-		return fmt.Sprintf("Error serializing data: %v", err)
-	}
-	return string(jsonData)
-}
pkg/bash/server.go
@@ -1,564 +1,130 @@
 package bash
 
 import (
-	"context"
-	"fmt"
-	"os"
-	"os/exec"
-	"os/user"
-	"path/filepath"
-	"runtime"
-	"strconv"
-	"strings"
-	"sync"
-	"time"
+  "fmt"
+  "os/exec"
+  "path/filepath"
+  "runtime"
 
-	"github.com/xlgmokha/mcp/pkg/mcp"
+  "github.com/xlgmokha/mcp/pkg/mcp"
 )
 
-// BashOperations provides bash command execution operations
-type BashOperations struct {
-	workingDir     string
-	commandHistory []CommandRecord
-	mu             sync.RWMutex
-	config         *Config
-}
-
-// Config holds server configuration
-type Config struct {
-	DefaultTimeout int    `json:"default_timeout"`
-	MaxTimeout     int    `json:"max_timeout"`
-	HistorySize    int    `json:"history_size"`
-	WorkingDir     string `json:"working_dir"`
-}
-
-// CommandRecord represents a command execution record
-type CommandRecord struct {
-	Timestamp   time.Time `json:"timestamp"`
-	Command     string    `json:"command"`
-	WorkingDir  string    `json:"working_dir"`
-	ExitCode    int       `json:"exit_code"`
-	Duration    int64     `json:"duration_ms"`
-	OutputSize  int       `json:"output_size_bytes"`
-}
-
-// ExecutionOptions defines options for command execution
-type ExecutionOptions struct {
-	Command       string            `json:"command"`
-	WorkingDir    string            `json:"working_dir,omitempty"`
-	Timeout       int               `json:"timeout,omitempty"`
-	Env           map[string]string `json:"env,omitempty"`
-	CaptureStderr bool              `json:"capture_stderr"`
-}
-
-// ExecutionResult contains the result of command execution
-type ExecutionResult struct {
-	Stdout        string `json:"stdout"`
-	Stderr        string `json:"stderr"`
-	ExitCode      int    `json:"exit_code"`
-	ExecutionTime int64  `json:"execution_time_ms"`
-	WorkingDir    string `json:"working_dir"`
-	Command       string `json:"command"`
-}
-
-// SystemInfo contains system information
-type SystemInfo struct {
-	Hostname     string `json:"hostname"`
-	OS           string `json:"os"`
-	Architecture string `json:"architecture"`
-	Kernel       string `json:"kernel"`
-	Shell        string `json:"shell"`
-	User         string `json:"user"`
-	Home         string `json:"home"`
-	Path         string `json:"path"`
-}
-
-// NewBashOperations creates a new BashOperations helper
-func NewBashOperations(config *Config) (*BashOperations, error) {
-	if config == nil {
-		config = DefaultConfig()
-	}
-
-	// Get current working directory if not specified
-	workingDir := config.WorkingDir
-	if workingDir == "" {
-		cwd, err := os.Getwd()
-		if err != nil {
-			return nil, fmt.Errorf("failed to get current working directory: %w", err)
-		}
-		workingDir = cwd
-	}
-
-	return &BashOperations{
-		workingDir:     workingDir,
-		commandHistory: make([]CommandRecord, 0, config.HistorySize),
-		config:         config,
-	}, nil
-}
-
-// New creates a new Bash MCP server
-func New(config *Config) (*mcp.Server, error) {
-	bash, err := NewBashOperations(config)
-	if err != nil {
-		return nil, err
-	}
-	
-	builder := mcp.NewServerBuilder("bash", "1.0.0")
-
-	// Add bash_exec tool
-	builder.AddTool(mcp.NewTool("bash_exec", "Execute a shell command and return output", map[string]interface{}{
-		"type": "object",
-		"properties": map[string]interface{}{
-			"command": map[string]interface{}{
-				"type":        "string",
-				"description": "Shell command to execute",
-			},
-			"working_dir": map[string]interface{}{
-				"type":        "string",
-				"description": "Working directory for command execution (optional)",
-			},
-			"timeout": map[string]interface{}{
-				"type":        "number",
-				"description": "Timeout in seconds (default: 30, max: 300)",
-			},
-			"capture_stderr": map[string]interface{}{
-				"type":        "boolean",
-				"description": "Include stderr in output (default: true)",
-			},
-			"env": map[string]interface{}{
-				"type":        "object",
-				"description": "Additional environment variables",
-			},
-		},
-		"required": []string{"command"},
-	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-		return bash.HandleBashExec(req)
-	}))
-
-	// Add bash_exec_stream tool
-	builder.AddTool(mcp.NewTool("bash_exec_stream", "Execute command with real-time output streaming", map[string]interface{}{
-		"type": "object",
-		"properties": map[string]interface{}{
-			"command": map[string]interface{}{
-				"type":        "string",
-				"description": "Shell command to execute",
-			},
-			"working_dir": map[string]interface{}{
-				"type":        "string",
-				"description": "Working directory for command execution (optional)",
-			},
-			"timeout": map[string]interface{}{
-				"type":        "number",
-				"description": "Timeout in seconds (default: 30, max: 300)",
-			},
-			"buffer_size": map[string]interface{}{
-				"type":        "number",
-				"description": "Stream buffer size in bytes",
-			},
-		},
-		"required": []string{"command"},
-	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-		return bash.HandleBashExecStream(req)
-	}))
-
-	// Add man_page tool
-	builder.AddTool(mcp.NewTool("man_page", "Get manual page for a command", map[string]interface{}{
-		"type": "object",
-		"properties": map[string]interface{}{
-			"command": map[string]interface{}{
-				"type":        "string",
-				"description": "Command to get manual for",
-			},
-			"section": map[string]interface{}{
-				"type":        "string",
-				"description": "Manual section (1-8)",
-			},
-		},
-		"required": []string{"command"},
-	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-		return bash.HandleManPage(req)
-	}))
-
-	// Add which_command tool
-	builder.AddTool(mcp.NewTool("which_command", "Find the location of a command", map[string]interface{}{
-		"type": "object",
-		"properties": map[string]interface{}{
-			"command": map[string]interface{}{
-				"type":        "string",
-				"description": "Command to locate",
-			},
-		},
-		"required": []string{"command"},
-	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-		return bash.HandleWhichCommand(req)
-	}))
-
-	// Add command_help tool
-	builder.AddTool(mcp.NewTool("command_help", "Get help text for a command (--help flag)", map[string]interface{}{
-		"type": "object",
-		"properties": map[string]interface{}{
-			"command": map[string]interface{}{
-				"type":        "string",
-				"description": "Command to get help for",
-			},
-		},
-		"required": []string{"command"},
-	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-		return bash.HandleCommandHelp(req)
-	}))
-
-	// Add get_env tool
-	builder.AddTool(mcp.NewTool("get_env", "Get environment variable value", map[string]interface{}{
-		"type": "object",
-		"properties": map[string]interface{}{
-			"variable": map[string]interface{}{
-				"type":        "string",
-				"description": "Environment variable name",
-			},
-			"all": map[string]interface{}{
-				"type":        "boolean",
-				"description": "Return all environment variables",
-			},
-		},
-	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-		return bash.HandleGetEnv(req)
-	}))
-
-	// Add get_working_dir tool
-	builder.AddTool(mcp.NewTool("get_working_dir", "Get the current working directory", map[string]interface{}{
-		"type": "object",
-	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-		return bash.HandleGetWorkingDir(req)
-	}))
-
-	// Add set_working_dir tool
-	builder.AddTool(mcp.NewTool("set_working_dir", "Set working directory for future commands", map[string]interface{}{
-		"type": "object",
-		"properties": map[string]interface{}{
-			"directory": map[string]interface{}{
-				"type":        "string",
-				"description": "Directory path to set as working directory",
-			},
-		},
-		"required": []string{"directory"},
-	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-		return bash.HandleSetWorkingDir(req)
-	}))
-
-	// Add system_info tool
-	builder.AddTool(mcp.NewTool("system_info", "Get basic system information", map[string]interface{}{
-		"type": "object",
-	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-		return bash.HandleSystemInfo(req)
-	}))
-
-	// Add process_info tool
-	builder.AddTool(mcp.NewTool("process_info", "Get information about running processes (ps command wrapper)", map[string]interface{}{
-		"type": "object",
-		"properties": map[string]interface{}{
-			"format": map[string]interface{}{
-				"type":        "string",
-				"description": "ps format string (default: aux)",
-			},
-			"filter": map[string]interface{}{
-				"type":        "string",
-				"description": "grep filter for processes",
-			},
-		},
-	}, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
-		return bash.HandleProcessInfo(req)
-	}))
-
-	// Add resources
-	builder.AddResource(mcp.NewResource("bash://system/info", "System Information", "application/json", func(req mcp.ReadResourceRequest) (mcp.ReadResourceResult, error) {
-		return bash.HandleSystemResource(req)
-	}))
-
-	builder.AddResource(mcp.NewResource("bash://history/recent", "Command History", "application/json", func(req mcp.ReadResourceRequest) (mcp.ReadResourceResult, error) {
-		return bash.HandleHistoryResource(req)
-	}))
-
-	builder.AddResource(mcp.NewResource("bash://env/all", "Environment Variables", "application/json", func(req mcp.ReadResourceRequest) (mcp.ReadResourceResult, error) {
-		return bash.HandleEnvResource(req)
-	}))
-
-	return builder.Build(), nil
-}
-
-// DefaultConfig returns default configuration
-func DefaultConfig() *Config {
-	return &Config{
-		DefaultTimeout: 30,
-		MaxTimeout:     300,
-		HistorySize:    100,
-		WorkingDir:     "",
-	}
-}
-
-// ConfigFromEnv creates configuration from environment variables
-func ConfigFromEnv() *Config {
-	config := DefaultConfig()
-
-	if timeout := os.Getenv("BASH_MCP_DEFAULT_TIMEOUT"); timeout != "" {
-		if val, err := strconv.Atoi(timeout); err == nil {
-			config.DefaultTimeout = val
-		}
-	}
-
-	if maxTimeout := os.Getenv("BASH_MCP_MAX_TIMEOUT"); maxTimeout != "" {
-		if val, err := strconv.Atoi(maxTimeout); err == nil {
-			config.MaxTimeout = val
-		}
-	}
-
-	if historySize := os.Getenv("BASH_MCP_MAX_HISTORY"); historySize != "" {
-		if val, err := strconv.Atoi(historySize); err == nil {
-			config.HistorySize = val
-		}
-	}
-
-	if workingDir := os.Getenv("BASH_MCP_WORKING_DIR"); workingDir != "" {
-		config.WorkingDir = workingDir
-	}
-
-	return config
-}
-
-
-
-// addCommandRecord adds a command record to history
-func (bash *BashOperations) addCommandRecord(record CommandRecord) {
-	bash.mu.Lock()
-	defer bash.mu.Unlock()
-
-	// Add record
-	bash.commandHistory = append(bash.commandHistory, record)
-
-	// Trim history if it exceeds the maximum size
-	if len(bash.commandHistory) > bash.config.HistorySize {
-		bash.commandHistory = bash.commandHistory[len(bash.commandHistory)-bash.config.HistorySize:]
-	}
-}
-
-// getCommandHistory returns a copy of the command history
-func (bash *BashOperations) getCommandHistory() []CommandRecord {
-	bash.mu.RLock()
-	defer bash.mu.RUnlock()
-
-	// Return a copy to avoid race conditions
-	history := make([]CommandRecord, len(bash.commandHistory))
-	copy(history, bash.commandHistory)
-	return history
-}
-
-// getWorkingDir returns the current working directory
-func (bash *BashOperations) getWorkingDir() string {
-	bash.mu.RLock()
-	defer bash.mu.RUnlock()
-	return bash.workingDir
-}
-
-// setWorkingDir sets the working directory
-func (bash *BashOperations) setWorkingDir(dir string) error {
-	// Validate directory exists
-	if _, err := os.Stat(dir); os.IsNotExist(err) {
-		return fmt.Errorf("directory does not exist: %s", dir)
-	}
-
-	// Convert to absolute path
-	absDir, err := filepath.Abs(dir)
-	if err != nil {
-		return fmt.Errorf("failed to get absolute path: %w", err)
-	}
-
-	bash.mu.Lock()
-	defer bash.mu.Unlock()
-	bash.workingDir = absDir
-	return nil
-}
-
-// executeCommand executes a shell command with the given options
-func (bash *BashOperations) executeCommand(options ExecutionOptions) (*ExecutionResult, error) {
-	startTime := time.Now()
-
-	// Set default timeout
-	if options.Timeout <= 0 {
-		options.Timeout = bash.config.DefaultTimeout
-	}
-
-	// Enforce maximum timeout
-	if options.Timeout > bash.config.MaxTimeout {
-		options.Timeout = bash.config.MaxTimeout
-	}
-
-	// Set working directory
-	workingDir := options.WorkingDir
-	if workingDir == "" {
-		workingDir = bash.getWorkingDir()
-	}
-
-	// Create context with timeout
-	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(options.Timeout)*time.Second)
-	defer cancel()
-
-	// Create command
-	var cmd *exec.Cmd
-	if runtime.GOOS == "windows" {
-		cmd = exec.CommandContext(ctx, "cmd", "/C", options.Command)
-	} else {
-		cmd = exec.CommandContext(ctx, "bash", "-c", options.Command)
-	}
-
-	// Set working directory
-	cmd.Dir = workingDir
-
-	// Set environment variables
-	if len(options.Env) > 0 {
-		env := os.Environ()
-		for key, value := range options.Env {
-			env = append(env, fmt.Sprintf("%s=%s", key, value))
-		}
-		cmd.Env = env
-	}
-
-	// Execute command
-	var stdout, stderr []byte
-	var err error
-
-	if options.CaptureStderr {
-		stdout, stderr, err = bash.runCommandWithStderr(cmd)
-	} else {
-		stdout, err = cmd.Output()
-	}
-
-	// Calculate execution time
-	executionTime := time.Since(startTime).Milliseconds()
-
-	// Get exit code
-	exitCode := 0
-	if err != nil {
-		if exitError, ok := err.(*exec.ExitError); ok {
-			exitCode = exitError.ExitCode()
-		} else {
-			exitCode = 1
-		}
-	}
-
-	// Create result
-	result := &ExecutionResult{
-		Stdout:        string(stdout),
-		Stderr:        string(stderr),
-		ExitCode:      exitCode,
-		ExecutionTime: executionTime,
-		WorkingDir:    workingDir,
-		Command:       options.Command,
-	}
-
-	// Add to command history
-	record := CommandRecord{
-		Timestamp:  startTime,
-		Command:    options.Command,
-		WorkingDir: workingDir,
-		ExitCode:   exitCode,
-		Duration:   executionTime,
-		OutputSize: len(stdout) + len(stderr),
-	}
-	bash.addCommandRecord(record)
-
-	return result, nil
-}
-
-// runCommandWithStderr runs a command and captures both stdout and stderr
-func (bash *BashOperations) runCommandWithStderr(cmd *exec.Cmd) ([]byte, []byte, error) {
-	var stdout, stderr []byte
-	var err error
-
-	stdoutPipe, err := cmd.StdoutPipe()
-	if err != nil {
-		return nil, nil, err
-	}
-
-	stderrPipe, err := cmd.StderrPipe()
-	if err != nil {
-		return nil, nil, err
-	}
-
-	if err := cmd.Start(); err != nil {
-		return nil, nil, err
-	}
-
-	// Read stdout and stderr concurrently
-	done := make(chan bool, 2)
-
-	go func() {
-		stdout, _ = readAll(stdoutPipe)
-		done <- true
-	}()
-
-	go func() {
-		stderr, _ = readAll(stderrPipe)
-		done <- true
-	}()
-
-	// Wait for both to complete
-	<-done
-	<-done
-
-	err = cmd.Wait()
-	return stdout, stderr, err
-}
-
-// readAll reads all data from a reader
-func readAll(r interface{ Read([]byte) (int, error) }) ([]byte, error) {
-	var data []byte
-	buf := make([]byte, 4096)
-	for {
-		n, err := r.Read(buf)
-		if n > 0 {
-			data = append(data, buf[:n]...)
-		}
-		if err != nil {
-			break
-		}
-	}
-	return data, nil
-}
-
-// getSystemInfo returns current system information
-func (bash *BashOperations) getSystemInfo() (*SystemInfo, error) {
-	hostname, _ := os.Hostname()
-	
-	currentUser, _ := user.Current()
-	username := "unknown"
-	home := "unknown"
-	if currentUser != nil {
-		username = currentUser.Username
-		home = currentUser.HomeDir
-	}
-
-	shell := os.Getenv("SHELL")
-	if shell == "" {
-		shell = "/bin/bash"
-	}
-
-	path := os.Getenv("PATH")
-
-	// Get kernel version
-	kernel := "unknown"
-	if runtime.GOOS == "linux" {
-		if result, err := bash.executeCommand(ExecutionOptions{Command: "uname -r", CaptureStderr: false}); err == nil {
-			kernel = strings.TrimSpace(result.Stdout)
-		}
-	}
-
-	return &SystemInfo{
-		Hostname:     hostname,
-		OS:           runtime.GOOS,
-		Architecture: runtime.GOARCH,
-		Kernel:       kernel,
-		Shell:        shell,
-		User:         username,
-		Home:         home,
-		Path:         path,
-	}, nil
+type Server struct {
+  workingDir string
+}
+
+func New(workingDir string) *mcp.Server {
+  if workingDir == "" {
+    workingDir = "."
+  }
+
+  absDir, err := filepath.Abs(workingDir)
+  if err != nil {
+    absDir = workingDir
+  }
+
+  bash := &Server{
+    workingDir: absDir,
+  }
+
+  builder := mcp.NewServerBuilder("bash", "1.0.0")
+
+  builder.AddTool(mcp.NewTool("exec", "Execute a shell command with streaming output", map[string]interface{}{
+    "type": "object",
+    "properties": map[string]interface{}{
+      "command": map[string]interface{}{
+        "type":        "string",
+        "description": "Shell command to execute",
+      },
+    },
+    "required": []string{"command"},
+  }, func(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+    return bash.handleExec(req)
+  }))
+
+  bashBuiltins := []string{
+    "alias", "bg", "bind", "break", "builtin", "caller", "cd", "command",
+    "compgen", "complete", "compopt", "continue", "declare", "dirs", "disown",
+    "echo", "enable", "eval", "exec", "exit", "export", "fc", "fg", "getopts",
+    "hash", "help", "history", "jobs", "kill", "let", "local", "logout",
+    "mapfile", "popd", "printf", "pushd", "pwd", "read", "readonly", "return",
+    "set", "shift", "shopt", "source", "suspend", "test", "times", "trap",
+    "type", "typeset", "ulimit", "umask", "unalias", "unset", "wait",
+  }
+
+  coreutils := []string{
+    "basename", "cat", "chgrp", "chmod", "chown", "cp", "cut", "date", "dd",
+    "df", "dirname", "du", "echo", "env", "expr", "false", "find", "grep",
+    "head", "hostname", "id", "kill", "ln", "ls", "mkdir", "mv", "ps", "pwd",
+    "rm", "rmdir", "sed", "sleep", "sort", "tail", "tar", "touch", "tr",
+    "true", "uname", "uniq", "wc", "which", "whoami", "xargs",
+  }
+
+  for _, builtin := range bashBuiltins {
+    builder.AddResource(mcp.NewResource(
+      fmt.Sprintf("bash://builtin/%s", builtin),
+      fmt.Sprintf("Bash builtin: %s", builtin),
+      "text/plain",
+      func(req mcp.ReadResourceRequest) (mcp.ReadResourceResult, error) {
+        return mcp.ReadResourceResult{
+          Contents: []mcp.Content{
+            mcp.NewTextContent(fmt.Sprintf("Bash builtin command: %s", builtin)),
+          },
+        }, nil
+      },
+    ))
+  }
+
+  for _, util := range coreutils {
+    builder.AddResource(mcp.NewResource(
+      fmt.Sprintf("bash://coreutil/%s", util),
+      fmt.Sprintf("Coreutil: %s", util),
+      "text/plain",
+      func(req mcp.ReadResourceRequest) (mcp.ReadResourceResult, error) {
+        return mcp.ReadResourceResult{
+          Contents: []mcp.Content{
+            mcp.NewTextContent(fmt.Sprintf("Core utility command: %s", util)),
+          },
+        }, nil
+      },
+    ))
+  }
+
+  return builder.Build()
+}
+
+func (s *Server) handleExec(req mcp.CallToolRequest) (mcp.CallToolResult, error) {
+  command, ok := req.Arguments["command"].(string)
+  if !ok {
+    return mcp.NewToolError("command argument is required and must be a string"), nil
+  }
+
+  if command == "" {
+    return mcp.NewToolError("command cannot be empty"), nil
+  }
+
+  var cmd *exec.Cmd
+  if runtime.GOOS == "windows" {
+    cmd = exec.Command("cmd", "/C", command)
+  } else {
+    cmd = exec.Command("bash", "-c", command)
+  }
+
+  cmd.Dir = s.workingDir
+
+  output, err := cmd.CombinedOutput()
+  if err != nil {
+    exitCode := 1
+    if exitError, ok := err.(*exec.ExitError); ok {
+      exitCode = exitError.ExitCode()
+    }
+
+    result := fmt.Sprintf("Command failed with exit code %d:\n%s", exitCode, string(output))
+    return mcp.CallToolResult{
+      Content: []mcp.Content{mcp.NewTextContent(result)},
+      IsError: exitCode != 0,
+    }, nil
+  }
+
+  return mcp.NewToolResult(mcp.NewTextContent(string(output))), nil
 }
\ No newline at end of file
pkg/bash/tools.go
@@ -1,4 +0,0 @@
-package bash
-
-// This file previously contained tool definitions that are now inlined in server.go
-// Keeping the file for potential future bash-specific utilities
\ No newline at end of file