Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,15 +166,15 @@ DSGo supports MCP clients for accessing external tools and services:
| Exa | `NewMCPExaClient(apiKey)` | HTTP | Web search and content extraction |
| Jina | `NewMCPJinaClient(apiKey)` | SSE | URL reading and content extraction |
| Tavily | `NewMCPTavilyClient(apiKey)` | HTTP | Web search and content extraction |
| Filesystem | `NewMCPFilesystemClient(dirs...)` | Stdio | Local filesystem operations via official MCP server |
| Filesystem | `NewMCPFilesystemClient(dir)` | Stdio | Local filesystem operations via official MCP server |

### Filesystem MCP Client

Uses the official `@modelcontextprotocol/server-filesystem` via npx/bunx:

```go
// Create filesystem client with allowed directories
fsClient, err := dsgo.NewMCPFilesystemClient("/path/to/dir1", "/path/to/dir2")
// Create filesystem client with specific directory
fsClient, err := dsgo.NewMCPFilesystemClient("/path/to/directory")

// Or use current directory (default)
fsClient, err := dsgo.NewMCPFilesystemClient()
Expand Down
4 changes: 3 additions & 1 deletion dsgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ var (
// Provides tavily-search and tavily-extract tools.
NewMCPTavilyClient = mcp.NewTavilyClient
// NewMCPFilesystemClient creates a new MCP client for local filesystem operations.
// Uses the official @modelcontextprotocol/server-filesystem via npx/bunx.
// The directory parameter specifies both allowed directory and working directory.
NewMCPFilesystemClient = mcp.NewFilesystemClient
// NewMCPHTTPTransport creates a new HTTP transport for MCP communication.
NewMCPHTTPTransport = mcp.NewHTTPTransport
Expand All @@ -285,6 +285,8 @@ var (
NewMCPSSETransportWithTimeouts = mcp.NewSSETransportWithTimeouts
// NewMCPStdioTransport creates a new stdio transport for MCP communication.
NewMCPStdioTransport = mcp.NewStdioTransport
// NewMCPStdioTransportWithDir creates a new stdio transport with a specific working directory.
NewMCPStdioTransportWithDir = mcp.NewStdioTransportWithDir
// NewMCPLocalTransport creates a local MCP transport.
NewMCPLocalTransport = mcp.NewLocalTransport
// NewMCPShellServer creates a built-in shell MCP server.
Expand Down
6 changes: 3 additions & 3 deletions examples/yaml_program/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,11 @@ func createMCPClient(ctx context.Context, name string, spec MCPSpec, timeouts Ti
if err != nil {
return nil, fmt.Errorf("failed to find project root: %w", err)
}
allowedDirs := []string{projectRoot}
allowedDir := projectRoot
if len(spec.AllowedDirs) > 0 {
allowedDirs = spec.AllowedDirs
allowedDir = spec.AllowedDirs[0] // Use first directory as primary
}
client, err := dsgo.NewMCPFilesystemClient(allowedDirs...)
client, err := dsgo.NewMCPFilesystemClient(allowedDir)
if err != nil {
return nil, fmt.Errorf("failed to create filesystem MCP client: %w", err)
}
Expand Down
21 changes: 11 additions & 10 deletions internal/mcp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,17 @@ func NewTavilyClient(apiKey string) (*Client, error) {

// NewFilesystemClient creates a new MCP client for local filesystem operations.
// Uses the official @modelcontextprotocol/server-filesystem via npx/bunx with stdio transport.
// allowedDirs specifies the directories that the filesystem server can access.
// If no directories are provided, defaults to current working directory.
func NewFilesystemClient(allowedDirs ...string) (*Client, error) {
// The directory parameter specifies both the allowed directory and the working directory
// for the MCP server, so relative paths resolve correctly within it.
// If empty, defaults to current working directory.
func NewFilesystemClient(directory string) (*Client, error) {
// Default to current directory if none specified
if len(allowedDirs) == 0 {
if directory == "" {
cwd, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("failed to get current directory: %w", err)
}
allowedDirs = []string{cwd}
directory = cwd
}

// Try bunx first (Bun's npx equivalent), fall back to npx
Expand All @@ -89,12 +90,12 @@ func NewFilesystemClient(allowedDirs ...string) (*Client, error) {
}
}

// Build args: npx -y @modelcontextprotocol/server-filesystem <dir1> <dir2> ...
args := []string{"-y", "@modelcontextprotocol/server-filesystem"}
args = append(args, allowedDirs...)
// Build args: npx -y @modelcontextprotocol/server-filesystem <directory>
args := []string{"-y", "@modelcontextprotocol/server-filesystem", directory}

// Create stdio transport
transport, err := NewStdioTransport(command, args, os.Environ())
// Create stdio transport with working directory set to the allowed directory.
// This ensures relative paths are resolved correctly from the directory root.
transport, err := NewStdioTransportWithDir(command, args, os.Environ(), directory)
if err != nil {
return nil, fmt.Errorf("failed to create stdio transport: %w", err)
}
Expand Down
9 changes: 9 additions & 0 deletions internal/mcp/transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -530,8 +530,17 @@ type StdioTransport struct {

// NewStdioTransport creates a new StdioTransport.
func NewStdioTransport(command string, args []string, env []string) (*StdioTransport, error) {
return NewStdioTransportWithDir(command, args, env, "")
}

// NewStdioTransportWithDir creates a new StdioTransport with a specific working directory.
// If dir is empty, the current working directory is used.
func NewStdioTransportWithDir(command string, args []string, env []string, dir string) (*StdioTransport, error) {
cmd := exec.Command(command, args...)
cmd.Env = env
if dir != "" {
cmd.Dir = dir
}

stdin, err := cmd.StdinPipe()
if err != nil {
Expand Down