summaryrefslogtreecommitdiff
path: root/pkg/bash/server.go
blob: 7854fc61dcadfabb90009a5f1931eb1ff85efb9b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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
}