package bash import ( "fmt" "os/exec" "path/filepath" "runtime" "github.com/xlgmokha/mcp/pkg/mcp" ) 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 }