From 953723dbc7bceca633d182eefc63a8ce8602ee11 Mon Sep 17 00:00:00 2001 From: Cheikh Seck Date: Tue, 17 Mar 2026 15:34:29 +0000 Subject: [PATCH] Remove path restrictions for node, bash, python commands; add MCP.md and reference in README --- MCP.md | 161 +++++++++++++++++++++++++++++++++++++++++++ README.md | 2 + internal/mcp/bash.go | 78 +-------------------- 3 files changed, 164 insertions(+), 77 deletions(-) create mode 100644 MCP.md diff --git a/MCP.md b/MCP.md new file mode 100644 index 0000000..5e3bc45 --- /dev/null +++ b/MCP.md @@ -0,0 +1,161 @@ +# MCP Integration Guide + +GoDex supports integrating with external MCP (Model Context Protocol) servers to extend its capabilities. This document explains how to configure external MCP servers. + +## Configuration + +External MCP servers are configured in your `config.yaml` file under the `mcp` section. + +### MCPServer Structure + +Each MCP server is defined with the following properties: + +| Field | Type | Description | +|-------|------|-------------| +| `name` | string | A friendly name for the server | +| `command` | string | The command to run the MCP server executable | +| `args` | []string | Optional command-line arguments | +| `env` | []string | Optional environment variables | +| `transport` | string | Transport type (e.g., `stdio`, `sse`) | +| `allowed_paths` | []string | Paths the server is allowed to access | + +### Example Configuration + +```yaml +mcp: + servers: + # Example: A custom filesystem server + filesystem: + command: "npx" + args: ["-y", "@modelcontextprotocol/server-filesystem", "/Users/myuser/docs"] + transport: "stdio" + allowed_paths: + - "/Users/myuser/docs" + - "/Users/myuser/shared" + + # Example: A GitHub MCP server + github: + command: "npx" + args: ["-y", "@modelcontextprotocol/server-github"] + transport: "stdio" + env: + - "GITHUB_PERSONAL_ACCESS_TOKEN=$GITHUB_TOKEN" +``` + +## Built-in MCP Servers + +GoDex includes several built-in MCP tools: + +### Bash + +Execute shell commands on your system. + +```yaml +mcp: + servers: + bash: + command: "godex" + args: ["mcp", "bash"] +``` + +### Filesystem + +Perform file operations (read, write, list, delete, search). + +```yaml +mcp: + servers: + filesystem: + command: "godex" + args: ["mcp", "filesystem"] + allowed_paths: + - "/home/user/projects" +``` + +### Webscraper + +Fetch URLs and extract content from web pages. + +```yaml +mcp: + servers: + webscraper: + command: "godex" + args: ["mcp", "webscraper"] +``` + +## Using MCP Servers in Conversations + +Once configured, you can use MCP servers in your conversations with GoDex. The AI agent will automatically discover and use available tools from your configured MCP servers. + +For example: + +``` +User: Can you read the contents of /home/user/projects/README.md? + +GoDex: (uses filesystem MCP server to read the file) +# Contents of README.md +... +``` + +## Environment Variables + +You can reference environment variables in your MCP server configuration using `$VAR_NAME` syntax: + +```yaml +mcp: + servers: + myserver: + command: "my-mcp-server" + env: + - "API_KEY=$MY_API_KEY" + - "DATABASE_URL=$DB_URL" +``` + +Make sure these environment variables are set before starting GoDex. + +## Transport Types + +### stdio + +The default transport type. Communicates with the MCP server via standard input/output. + +```yaml +transport: "stdio" +``` + +### SSE (Server-Sent Events) + +For HTTP-based MCP servers. + +```yaml +transport: "sse" +url: "http://localhost:8080/sse" +``` + +## Security + +- Only use MCP servers from trusted sources +- Review the `allowed_paths` configuration to restrict filesystem access +- Be cautious when passing sensitive data via environment variables +- MCP servers run with the same permissions as the GoDex process + +## Troubleshooting + +### Server fails to start + +- Verify the command path is correct +- Check that required dependencies are installed +- Review the server logs for error messages + +### Tools not available + +- Ensure the MCP server started successfully +- Check that the server configuration is valid YAML +- Verify the transport type matches the server's requirements + +### Permission denied errors + +- Check that the paths in `allowed_paths` exist +- Verify file/directory permissions +- Ensure the MCP server process has necessary access rights diff --git a/README.md b/README.md index 8b24b1e..5d5991a 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,8 @@ GoDex includes built-in MCP servers: | `bash` | Run shell commands, Python, Node.js | | `webscraper` | Fetch URLs with JavaScript rendering, search HTML, extract links | +For detailed MCP configuration including external servers, see [MCP.md](MCP.md). + #### Adding Allowed Paths By default, MCP servers only allow access to the current working directory. Add more allowed paths: diff --git a/internal/mcp/bash.go b/internal/mcp/bash.go index e3e4b4d..b02bb56 100644 --- a/internal/mcp/bash.go +++ b/internal/mcp/bash.go @@ -107,18 +107,9 @@ func (s *BashServer) isPathAllowed(path string) bool { } var ( - cdRegex = regexp.MustCompile(`(?i)^cd\s+(\S+)`) - pythonPathOp = regexp.MustCompile(`(?i)(?:os\.chdir|os\.mkdir|os\.makedirs|os\.rmdir|os\.remove|os\.rename|shutil\.move|shutil\.copy|open)\s*\(\s*['"]`) - nodePathOp = regexp.MustCompile(`(?i)(?:process\.chdir|fs\.(?:mkdir|rmdir|unlink|rename|writeFile|readFile)|fs\.promises\.(?:mkdir|rmdir|unlink|rename|writeFile|readFile))\s*\(\s*['"]`) + cdRegex = regexp.MustCompile(`(?i)^cd\s+(\S+)`) ) -var pathCommands = []string{ - "cat", "ls", "rm", "rmdir", "mv", "cp", "touch", "chmod", "chown", "chgrp", - "head", "tail", "less", "more", "grep", "rg", "find", "stat", "file", - "mkdir", "tar", "zip", "unzip", "gzip", "gunzip", "xz", "diff", "cmp", - "du", "df", "ln", "readlink", "realpath", "which", "whereis", -} - func (s *BashServer) isCommandAllowed(command string) bool { trimmed := strings.TrimSpace(command) @@ -132,65 +123,6 @@ func (s *BashServer) isCommandAllowed(command string) bool { } } - for _, cmd := range pathCommands { - re := regexp.MustCompile(fmt.Sprintf(`(?i)^%s\s+(\S+)`, cmd)) - match := re.FindStringSubmatch(trimmed) - if len(match) > 1 { - targetPath := match[1] - targetPath = strings.ReplaceAll(targetPath, "~", os.Getenv("HOME")) - targetPath = filepath.Clean(targetPath) - if !s.isPathAllowed(targetPath) { - return false - } - } - reWithFlags := regexp.MustCompile(fmt.Sprintf(`(?i)^%s\s+-[a-zA-Z]+\s+(\S+)`, cmd)) - match = reWithFlags.FindStringSubmatch(trimmed) - if len(match) > 1 { - targetPath := match[1] - targetPath = strings.ReplaceAll(targetPath, "~", os.Getenv("HOME")) - targetPath = filepath.Clean(targetPath) - if !s.isPathAllowed(targetPath) { - return false - } - } - } - - return true -} - -func (s *BashServer) isPythonCodeAllowed(code string) bool { - indices := pythonPathOp.FindAllStringIndex(code, -1) - for _, loc := range indices { - quote := code[loc[1]] - endIdx := loc[1] + 1 - for endIdx < len(code) && code[endIdx] != quote { - endIdx++ - } - path := code[loc[1]+1 : endIdx] - path = strings.ReplaceAll(path, "~", os.Getenv("HOME")) - path = filepath.Clean(path) - if !s.isPathAllowed(path) { - return false - } - } - return true -} - -func (s *BashServer) isNodeCodeAllowed(code string) bool { - indices := nodePathOp.FindAllStringIndex(code, -1) - for _, loc := range indices { - quote := code[loc[1]] - endIdx := loc[1] + 1 - for endIdx < len(code) && code[endIdx] != quote { - endIdx++ - } - path := code[loc[1]+1 : endIdx] - path = strings.ReplaceAll(path, "~", os.Getenv("HOME")) - path = filepath.Clean(path) - if !s.isPathAllowed(path) { - return false - } - } return true } @@ -360,10 +292,6 @@ func (s *BashServer) runPython(ctx context.Context, args map[string]interface{}) return "", fmt.Errorf("code is required") } - if !s.isPythonCodeAllowed(code) { - return "", fmt.Errorf("code not allowed: must operate within allowed paths: %s", strings.Join(s.allowedPaths, ", ")) - } - timeout := 30 if t, ok := args["timeout"].(float64); ok { timeout = int(t) @@ -388,10 +316,6 @@ func (s *BashServer) runNode(ctx context.Context, args map[string]interface{}) ( return "", fmt.Errorf("code is required") } - if !s.isNodeCodeAllowed(code) { - return "", fmt.Errorf("code not allowed: must operate within allowed paths: %s", strings.Join(s.allowedPaths, ", ")) - } - timeout := 30 if t, ok := args["timeout"].(float64); ok { timeout = int(t)