diff --git a/REFERENCE.md b/REFERENCE.md index d0db46b..1e390e1 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -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() diff --git a/dsgo.go b/dsgo.go index 3b8d839..cfa8985 100644 --- a/dsgo.go +++ b/dsgo.go @@ -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 @@ -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. diff --git a/examples/yaml_program/tools.go b/examples/yaml_program/tools.go index 75cd4cd..6e5dcbb 100644 --- a/examples/yaml_program/tools.go +++ b/examples/yaml_program/tools.go @@ -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) } diff --git a/internal/mcp/client.go b/internal/mcp/client.go index 126e6f6..fff79f1 100644 --- a/internal/mcp/client.go +++ b/internal/mcp/client.go @@ -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 @@ -89,12 +90,12 @@ func NewFilesystemClient(allowedDirs ...string) (*Client, error) { } } - // Build args: npx -y @modelcontextprotocol/server-filesystem ... - args := []string{"-y", "@modelcontextprotocol/server-filesystem"} - args = append(args, allowedDirs...) + // Build args: npx -y @modelcontextprotocol/server-filesystem + 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) } diff --git a/internal/mcp/transport.go b/internal/mcp/transport.go index 093386b..db219bd 100644 --- a/internal/mcp/transport.go +++ b/internal/mcp/transport.go @@ -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 {