Add built-in filesystem MCP server and enhance MCP command support

- Introduced a built-in filesystem implementation as a standalone MCP server accessible via stdio.
- Updated MCP command to include 'serve' subcommand for managing the filesystem server.
- Enhanced shell completion for MCP commands to include new options.
- Added detailed documentation for the built-in filesystem MCP server.
This commit is contained in:
Cheikh Seck committed 2026-04-23 22:53:32 +00:00
1 parent 576e2243cb
commit a64e0ac7b5
6 files changed
+433 -5

No files matched your search

+10
View File
@@ -94,6 +94,16 @@ mcp_servers:
## External MCP Servers
## Built-in filesystem as an MCP server (stdio)
GoDex can run its built-in filesystem implementation as a standalone MCP server over stdio:
```bash
godex mcp serve filesystem --allowed-path /home/user/project --allowed-path /tmp
```
Details: [MCP_BUILTIN_FILESYSTEM_SERVER.md](MCP_BUILTIN_FILESYSTEM_SERVER.md)
### Chrome MCP Controller
Control Chrome tabs via a browser extension. Requires the [chrome-mcp](https://github.com/cheikh2shift/chrome-mcp) server and Chrome extension.
+181
View File
@@ -0,0 +1,181 @@
# Built-in filesystem MCP server (`godex mcp serve filesystem`)
GoDex contains a built-in filesystem tool implementation.
This page documents how to run that built-in filesystem as a **standalone MCP server over stdio**, so other AI agents/clients can connect to it.
## Run
```bash
godex mcp serve filesystem
```
## Permissions (roots + allowlist)
This server supports common MCP permission workflows used by other agents:
- **Client roots** (recommended): when the connected client supports MCP *roots*, GoDex will request the roots list and treat them as allowed paths.
- **Explicit allowlist**: pass `--allowed-path` (repeatable) to add allowed paths up front.
Examples:
```bash
# Explicit allowlist
godex mcp serve filesystem --allowed-path /home/user/project --allowed-path /tmp
# Disable roots usage (only use explicit allowlist / cwd fallback)
godex mcp serve filesystem --use-roots=false --allowed-path /tmp
```
`--auto-confirm` (unsafe) will automatically expand allowed paths when a request hits a restricted path.
## Configure another agent/client
Any MCP client that can launch stdio servers can run:
```text
command: godex
args: ["mcp", "serve", "filesystem", "--allowed-path", "/path/to/workspace"]
transport: stdio
```
## Tools
The server exposes a subset of GoDex’s filesystem tools, including:
- `read_file`, `write_file`, `list_directory`
- `create_directory`, `delete_file`
- `search_files`, `search_in_file`, `search_directory_text`, `search_file_text`
- `get_file_info`, `read_file_line_range`
- `replace_first_in_file`, `replace_line_range`, `insert_at_line`
- `list_allowed_paths`
## Add to OpenCode
### Via config file
In your `opencode.json` (project) or `~/.config/opencode/opencode.json` (global):
```jsonc
{
"mcp": {
"godex-filesystem": {
"type": "local",
"command": ["godex", "mcp", "serve", "filesystem", "--allowed-path", "/path/to/workspace"],
"enabled": true
}
}
}
```
Then restart OpenCode.
### Via CLI
```bash
npx @anthropic/anthropic-cli mcp add
# Or if opencode has the same tool:
opencode mcp add
```
## Add to Claude Desktop
Edit `claude_desktop_config.json` (location varies by OS):
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Linux: `~/.config/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"godex-filesystem": {
"command": "godex",
"args": ["mcp", "serve", "filesystem", "--allowed-path", "/path/to/workspace"],
"env": {}
}
}
}
```
## Add to Claude Code
```bash
claude mcp add godex-filesystem --transport stdio -- godex mcp serve filesystem --allowed-path /path/to/workspace
```
Or via config in `.mcp.json` (project) or `~/.claude.json` (global):
```jsonc
{
"mcp": {
"godex-filesystem": {
"command": ["godex", "mcp", "serve", "filesystem", "--allowed-path", "/path/to/workspace"],
"env": {}
}
}
}
```
## Add to Codex
Run:
```bash
codex mcp add godex-filesystem -- godex mcp serve filesystem --allowed-path /path/to/workspace
```
Or edit `~/.codex/config.toml`:
```toml
[mcp_servers.godex-filesystem]
command = "godex"
args = ["mcp", "serve", "filesystem", "--allowed-path", "/path/to/workspace"]
```
For workspace-specific config, create `.codex/config.toml` in your project.
## Add to Cursor
Cursor doesn't have a CLI command for adding MCP servers. Edit `~/.cursor/mcp.json`:
```json
{
"mcpServers": {
"godex-filesystem": {
"command": "godex",
"args": ["mcp", "serve", "filesystem", "--allowed-path", "/path/to/workspace"]
}
}
}
```
Or add via Settings > Tools & MCP > Add > Enter custom server details.
## Add to VS Code
### Via Command Palette
1. Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on Mac)
2. Search **MCP: Add Server**
3. Select **Local** (stdio)
4. Enter command: `godex mcp serve filesystem --allowed-path /path/to/workspace`
### Via CLI
```bash
code --add-mcp '{"name":"godex-filesystem","command":"godex","args":["mcp","serve","filesystem","--allowed-path","/path/to/workspace"]}'
```
### Via config file
Edit `.vscode/mcp.json` (workspace) or run **MCP: Open User Configuration** to edit global config:
```json
{
"servers": {
"godex-filesystem": {
"command": "godex",
"args": ["mcp", "serve", "filesystem", "--allowed-path", "/path/to/workspace"]
}
}
}
```
+2
View File
@@ -222,6 +222,8 @@ GoDex includes built-in MCP servers:
For detailed MCP configuration including external servers, see [MCP.md](MCP.md).
To expose the built-in filesystem MCP server to other agents/clients over stdio, see [MCP_BUILTIN_FILESYSTEM_SERVER.md](MCP_BUILTIN_FILESYSTEM_SERVER.md).
### Hive Network
GoDex supports a Hive network mode where multiple instances can delegate tasks to each other. See [HIVE.md](HIVE.md) for details.
+6 -2
View File
@@ -583,7 +583,7 @@ complete -c godex -l debug -s d -f
complete -c godex -l completion -x -a "bash zsh fish" -d "Generate shell completion"
complete -c godex -l llama-server -s l -x -d "External llama.cpp server URL"
complete -c godex -n "__fish_use_subcommand" -a "mcp" -d "Manage MCP servers"
complete -c godex -n "__fish_seen_subcommand_from mcp" -a "add remove" -d "MCP subcommand"
complete -c godex -n "__fish_seen_subcommand_from mcp" -a "add remove serve" -d "MCP subcommand"
complete -c godex -n "__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from add" -l provider -x -a "(__godex_complete_providers)"
complete -c godex -n "__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from add" -l name -x -a "filesystem bash webscraper"
complete -c godex -n "__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from add" -l command -x
@@ -594,8 +594,12 @@ complete -c godex -n "__fish_seen_subcommand_from mcp; and __fish_seen_subcomman
complete -c godex -n "__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from add" -l allowed-url -x
complete -c godex -n "__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from remove" -l provider -x -a "(__godex_complete_providers)"
complete -c godex -n "__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from remove" -l name -x -a "(__godex_complete_mcp_servers)"
complete -c godex -n "__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from serve" -a "filesystem" -d "Serve built-in MCP server"
complete -c godex -n "__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from serve; and __fish_seen_subcommand_from filesystem" -l allowed-path -x -a "(__fish_complete_directories)"
complete -c godex -n "__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from serve; and __fish_seen_subcommand_from filesystem" -l auto-confirm -f
complete -c godex -n "__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from serve; and __fish_seen_subcommand_from filesystem" -l use-roots -x -a "true false"
`, defaultConfigPath, defaultConfigPath, defaultConfigPath)
}
}
func printCompletionHelp() {
fmt.Print(`Shell completion for godex
+6 -3
View File
@@ -1323,6 +1323,7 @@ Flags:
MCP subcommands:
godex mcp add --provider <name> --name <server> [options]
godex mcp remove --provider <name> --name <server>
godex mcp serve filesystem [options]
MCP add options:
--name MCP server name (filesystem, bash, webscraper, or external name)
@@ -1347,7 +1348,7 @@ func (s *stringList) Set(value string) error {
func handleMCPSubcommand(args []string, configPath string) error {
if len(args) == 0 {
return fmt.Errorf("usage: godex mcp <add|remove> [options]")
return fmt.Errorf("usage: godex mcp <add|remove|serve> [options]")
}
isFilesystem := func(name string) bool {
@@ -1444,8 +1445,10 @@ func handleMCPSubcommand(args []string, configPath string) error {
if err := config.Save(configPath, cfg); err != nil {
return err
}
fmt.Printf("Added MCP server %q to provider %q\n", serverName, provider.Name)
return nil
fmt.Printf("Added MCP server %q to provider %q\n", serverName, provider.Name)
return nil
case "serve":
return handleMCPServeSubcommand(args[1:])
case "remove":
fs := flag.NewFlagSet("mcp remove", flag.ContinueOnError)
var providerName string
+228
View File
@@ -0,0 +1,228 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"strings"
"github.com/cheikh2shift/godex/internal/mcp"
mcplib "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func handleMCPServeSubcommand(args []string) error {
if len(args) == 0 {
return fmt.Errorf("usage: godex mcp serve filesystem [options]")
}
switch strings.ToLower(strings.TrimSpace(args[0])) {
case "filesystem", "files":
fs := flag.NewFlagSet("mcp serve filesystem", flag.ContinueOnError)
var allowedPaths stringList
var autoConfirm bool
var useRoots bool
fs.Var(&allowedPaths, "allowed-path", "allowed path (repeatable); if omitted, falls back to client roots or cwd")
fs.BoolVar(&autoConfirm, "auto-confirm", false, "auto-confirm restricted paths (unsafe)")
fs.BoolVar(&useRoots, "use-roots", true, "use client-provided MCP roots as additional allowed paths")
if err := fs.Parse(args[1:]); err != nil {
return err
}
return serveFilesystemMCP([]string(allowedPaths), autoConfirm, useRoots)
default:
return fmt.Errorf("unknown server: %s (supported: filesystem)", args[0])
}
}
func serveFilesystemMCP(baseAllowedPaths []string, autoConfirm bool, useRoots bool) error {
s := server.NewMCPServer(
"godex-filesystem",
version,
server.WithToolCapabilities(true),
server.WithRoots(),
)
resolveAllowedPaths := func(ctx context.Context) []string {
paths := append([]string{}, baseAllowedPaths...)
if useRoots {
if res, err := s.RequestRoots(ctx, mcplib.ListRootsRequest{}); err == nil {
for _, r := range res.Roots {
if strings.TrimSpace(r.URI) == "" {
continue
}
// Roots are usually file:// URIs, but some clients may send plain paths.
p := strings.TrimPrefix(r.URI, "file://")
if strings.TrimSpace(p) == "" {
continue
}
paths = append(paths, p)
}
}
}
if len(paths) == 0 {
if wd, err := os.Getwd(); err == nil && strings.TrimSpace(wd) != "" {
paths = []string{wd}
}
}
return uniqueStrings(paths)
}
addProxyTool := func(name string, description string, schema mcplib.ToolInputSchema) {
s.AddTool(mcplib.Tool{
Name: name,
Description: description,
InputSchema: schema,
}, func(ctx context.Context, request mcplib.CallToolRequest) (*mcplib.CallToolResult, error) {
allowed := resolveAllowedPaths(ctx)
fs := mcp.NewFileSystemServer(allowed, autoConfirm)
args, _ := request.Params.Arguments.(map[string]any)
argMap := make(map[string]interface{}, len(args))
for k, v := range args {
argMap[k] = v
}
out, err := fs.CallTool(ctx, name, argMap)
if err != nil {
return mcplib.NewToolResultError(err.Error()), nil
}
return mcplib.NewToolResultText(out), nil
})
}
// Permissions helpers (works well with other agents' root/permission prompts).
s.AddTool(mcplib.Tool{
Name: "list_allowed_paths",
Description: "List currently allowed filesystem paths (union of server flags and client-provided roots).",
InputSchema: mcplib.ToolInputSchema{Type: "object", Properties: map[string]any{}},
}, func(ctx context.Context, request mcplib.CallToolRequest) (*mcplib.CallToolResult, error) {
paths := resolveAllowedPaths(ctx)
return mcplib.NewToolResultText(strings.Join(paths, "\n")), nil
})
// Core filesystem tools (proxy to internal built-in implementation).
addProxyTool("read_file", "Read contents of a file.", mcplib.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"path": map[string]any{"type": "string", "description": "Absolute path to the file."},
},
Required: []string{"path"},
})
addProxyTool("write_file", "Write content to a file.", mcplib.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"path": map[string]any{"type": "string", "description": "Absolute path to the file."},
"content": map[string]any{"type": "string", "description": "Content to write."},
},
Required: []string{"path", "content"},
})
addProxyTool("list_directory", "List files in a directory.", mcplib.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"path": map[string]any{"type": "string", "description": "Absolute path to directory."},
},
Required: []string{"path"},
})
addProxyTool("create_directory", "Create a new directory.", mcplib.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"path": map[string]any{"type": "string", "description": "Directory path to create."},
},
Required: []string{"path"},
})
addProxyTool("delete_file", "Delete a file or directory.", mcplib.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"path": map[string]any{"type": "string", "description": "File or directory path to delete."},
},
Required: []string{"path"},
})
addProxyTool("search_files", "Search for files matching a glob pattern.", mcplib.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"path": map[string]any{"type": "string", "description": "Directory to search."},
"pattern": map[string]any{"type": "string", "description": "File pattern (e.g., *.go)."},
},
Required: []string{"path", "pattern"},
})
addProxyTool("get_file_info", "Get information about a file.", mcplib.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"path": map[string]any{"type": "string", "description": "File path."},
},
Required: []string{"path"},
})
addProxyTool("read_file_line_range", "Read a specific range of lines from a file.", mcplib.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"path": map[string]any{"type": "string", "description": "File path."},
"start": map[string]any{"type": "number", "description": "Start line number (1-indexed)."},
"end": map[string]any{"type": "number", "description": "End line number (inclusive)."},
},
Required: []string{"path", "start", "end"},
})
addProxyTool("replace_first_in_file", "Replace the first occurrence of a string in a file.", mcplib.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"path": map[string]any{"type": "string", "description": "File path."},
"find": map[string]any{"type": "string", "description": "Text to find (plain string)."},
"replace": map[string]any{"type": "string", "description": "Replacement text."},
},
Required: []string{"path", "find", "replace"},
})
addProxyTool("replace_line_range", "Replace a range of lines in a file.", mcplib.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"path": map[string]any{"type": "string", "description": "File path."},
"start": map[string]any{"type": "number", "description": "Start line number (1-indexed)."},
"end": map[string]any{"type": "number", "description": "End line number (inclusive)."},
"new_content": map[string]any{"type": "string", "description": "New content to insert at the deleted location."},
},
Required: []string{"path", "start", "end", "new_content"},
})
addProxyTool("insert_at_line", "Insert content at a specific line number.", mcplib.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"path": map[string]any{"type": "string", "description": "File path."},
"line": map[string]any{"type": "number", "description": "Line number to insert at (1-indexed, 0 = append at end)."},
"content": map[string]any{"type": "string", "description": "Content to insert."},
},
Required: []string{"path", "line", "content"},
})
addProxyTool("search_file_text", "Search for text content within files in a directory.", mcplib.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"path": map[string]any{"type": "string", "description": "Directory to search."},
"query": map[string]any{"type": "string", "description": "Text to search for."},
"case_sensitive": map[string]any{"type": "boolean", "description": "Enable case-sensitive matching."},
"use_regex": map[string]any{"type": "boolean", "description": "Treat query as a regular expression."},
},
Required: []string{"path", "query"},
})
addProxyTool("search_in_file", "Search for text within a specific file.", mcplib.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"path": map[string]any{"type": "string", "description": "File path."},
"query": map[string]any{"type": "string", "description": "Text to search for (ignored if pattern provided)."},
"pattern": map[string]any{"type": "string", "description": "Regex pattern (takes precedence)."},
"case_sensitive": map[string]any{"type": "boolean", "description": "Enable case-sensitive matching."},
"use_regex": map[string]any{"type": "boolean", "description": "Treat query as a regular expression."},
},
Required: []string{"path"},
})
addProxyTool("search_directory_text", "Search for text content within files in a directory.", mcplib.ToolInputSchema{
Type: "object",
Properties: map[string]any{
"path": map[string]any{"type": "string", "description": "Directory to search."},
"query": map[string]any{"type": "string", "description": "Text to search for (ignored if pattern is provided)."},
"pattern": map[string]any{"type": "string", "description": "Regex pattern to search for (takes precedence over query)."},
"case_sensitive": map[string]any{"type": "boolean", "description": "Enable case-sensitive matching."},
},
Required: []string{"path"},
})
log.Printf("[MCP] Serving built-in filesystem MCP server over stdio (autoConfirm=%v, useRoots=%v, baseAllowedPaths=%v)", autoConfirm, useRoots, baseAllowedPaths)
return server.ServeStdio(s)
}