#
tokens: 34698/50000 42/42 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .github
│   ├── dependabot.yml
│   └── workflows
│       ├── release.yml
│       └── test.yml
├── .gitignore
├── .goreleaser.yml
├── Dockerfile
├── filesystemserver
│   ├── handler
│   │   ├── copy_file_test.go
│   │   ├── copy_file.go
│   │   ├── create_directory_test.go
│   │   ├── create_directory.go
│   │   ├── delete_file_test.go
│   │   ├── delete_file.go
│   │   ├── get_file_info_test.go
│   │   ├── get_file_info.go
│   │   ├── handler_test.go
│   │   ├── handler.go
│   │   ├── helper.go
│   │   ├── list_allowed_directories_test.go
│   │   ├── list_allowed_directories.go
│   │   ├── list_directory_test.go
│   │   ├── list_directory.go
│   │   ├── modify_file.go
│   │   ├── move_file.go
│   │   ├── read_file_test.go
│   │   ├── read_file.go
│   │   ├── read_multiple_files_test.go
│   │   ├── read_multiple_files.go
│   │   ├── resources.go
│   │   ├── search_files_test.go
│   │   ├── search_files.go
│   │   ├── search_within_files.go
│   │   ├── tree_test.go
│   │   ├── tree.go
│   │   ├── types.go
│   │   └── write_file.go
│   ├── inprocess_test.go
│   ├── server_test.go
│   ├── server.go
│   └── utils_test.go
├── go.mod
├── go.sum
├── LICENSE
├── main.go
├── README.md
└── smithery.yaml
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
.aider*
.opencode*
OpenCode.md
dist/
CLAUDE.md
.claude/

# go build artifact
mcp-filesystem-server

```

--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------

```yaml
version: 2

before:
  hooks:
    - go mod tidy

builds:
  - id: mcp-filesystem-server
    env:
      - CGO_ENABLED=0
    goos:
      - linux
      - windows
      - darwin
    goarch:
      - amd64
      - arm64
    ldflags:
      - -s -w -X github.com/mark3labs/mcp-filesystem-server/filesystemserver.Version={{.Version}}
    binary: mcp-filesystem-server
    main: .

archives:
  - id: default
    format_overrides:
      - goos: windows
        formats:
          - zip
    name_template: >-
      {{ .ProjectName }}_
      {{- .Os }}_
      {{- .Arch }}
    files:
      - README.md
      - LICENSE*

checksum:
  name_template: 'checksums.txt'
  algorithm: sha256

# Using new snapshot configuration
snapshot:
  version_template: "{{ .Version }}-SNAPSHOT-{{ .ShortCommit }}"

changelog:
  sort: asc
  filters:
    exclude:
      - '^docs:'
      - '^test:'
      - Merge pull request
      - Merge branch

release:
  github:
    owner: mark3labs
    name: mcp-filesystem-server
  draft: false
  prerelease: auto
  name_template: "{{ .Tag }}"
  mode: replace

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# MCP Filesystem Server

This MCP server provides secure access to the local filesystem via the Model Context Protocol (MCP).

## Components

### Resources

- **file://**
  - Name: File System
  - Description: Access to files and directories on the local file system

### Tools

#### File Operations

- **read_file**
  - Read the complete contents of a file from the file system
  - Parameters: `path` (required): Path to the file to read

- **read_multiple_files**
  - Read the contents of multiple files in a single operation
  - Parameters: `paths` (required): List of file paths to read

- **write_file**
  - Create a new file or overwrite an existing file with new content
  - Parameters: `path` (required): Path where to write the file, `content` (required): Content to write to the file

- **copy_file**
  - Copy files and directories
  - Parameters: `source` (required): Source path of the file or directory, `destination` (required): Destination path

- **move_file**
  - Move or rename files and directories
  - Parameters: `source` (required): Source path of the file or directory, `destination` (required): Destination path

- **delete_file**
  - Delete a file or directory from the file system
  - Parameters: `path` (required): Path to the file or directory to delete, `recursive` (optional): Whether to recursively delete directories (default: false)

- **modify_file**
  - Update file by finding and replacing text using string matching or regex
  - Parameters: `path` (required): Path to the file to modify, `find` (required): Text to search for, `replace` (required): Text to replace with, `all_occurrences` (optional): Replace all occurrences (default: true), `regex` (optional): Treat find pattern as regex (default: false)

#### Directory Operations

- **list_directory**
  - Get a detailed listing of all files and directories in a specified path
  - Parameters: `path` (required): Path of the directory to list

- **create_directory**
  - Create a new directory or ensure a directory exists
  - Parameters: `path` (required): Path of the directory to create

- **tree**
  - Returns a hierarchical JSON representation of a directory structure
  - Parameters: `path` (required): Path of the directory to traverse, `depth` (optional): Maximum depth to traverse (default: 3), `follow_symlinks` (optional): Whether to follow symbolic links (default: false)

#### Search and Information

- **search_files**
  - Recursively search for files and directories matching a pattern
  - Parameters: `path` (required): Starting path for the search, `pattern` (required): Search pattern to match against file names

- **search_within_files**
  - Search for text within file contents across directory trees
  - Parameters: `path` (required): Starting directory for the search, `substring` (required): Text to search for within file contents, `depth` (optional): Maximum directory depth to search, `max_results` (optional): Maximum number of results to return (default: 1000)

- **get_file_info**
  - Retrieve detailed metadata about a file or directory
  - Parameters: `path` (required): Path to the file or directory

- **list_allowed_directories**
  - Returns the list of directories that this server is allowed to access
  - Parameters: None

## Features

- Secure access to specified directories
- Path validation to prevent directory traversal attacks
- Symlink resolution with security checks
- MIME type detection
- Support for text, binary, and image files
- Size limits for inline content and base64 encoding

## Getting Started

### Installation

#### Using Go Install

```bash
go install github.com/mark3labs/mcp-filesystem-server@latest
```

### Usage

#### As a standalone server

Start the MCP server with allowed directories:

```bash
mcp-filesystem-server /path/to/allowed/directory [/another/allowed/directory ...]
```

#### As a library in your Go project

```go
package main

import (
	"log"
	"os"

	"github.com/mark3labs/mcp-filesystem-server/filesystemserver"
)

func main() {
	// Create a new filesystem server with allowed directories
	allowedDirs := []string{"/path/to/allowed/directory", "/another/allowed/directory"}
	fs, err := filesystemserver.NewFilesystemServer(allowedDirs)
	if err != nil {
		log.Fatalf("Failed to create server: %v", err)
	}

	// Serve requests
	if err := fs.Serve(); err != nil {
		log.Fatalf("Server error: %v", err)
	}
}
```

### Usage with Model Context Protocol

To integrate this server with apps that support MCP:

```json
{
  "mcpServers": {
    "filesystem": {
      "command": "mcp-filesystem-server",
      "args": ["/path/to/allowed/directory", "/another/allowed/directory"]
    }
  }
}
```

### Docker

#### Running with Docker

You can run the Filesystem MCP server using Docker:

```bash
docker run -i --rm ghcr.io/mark3labs/mcp-filesystem-server:latest /path/to/allowed/directory
```

#### Docker Configuration with MCP

To integrate the Docker image with apps that support MCP:

```json
{
  "mcpServers": {
    "filesystem": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "ghcr.io/mark3labs/mcp-filesystem-server:latest",
        "/path/to/allowed/directory"
      ]
    }
  }
}
```

If you need changes made inside the container to reflect on the host filesystem, you can mount a volume. This allows the container to access and modify files on the host system. Here's an example:

```json
{
  "mcpServers": {
    "filesystem": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "--volume=/allowed/directory/in/host:/allowed/directory/in/container",
        "ghcr.io/mark3labs/mcp-filesystem-server:latest",
        "/allowed/directory/in/container"
      ]
    }
  }
}
```

## License

See the [LICENSE](LICENSE) file for details.

```

--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------

```yaml
version: 2
updates:
  - package-ecosystem: "gomod"
    directory: "/"
    schedule:
      interval: "daily"
    groups:
      minor-dependencies:
        update-types:
          - "minor"
          - "patch"
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "daily"

```

--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------

```yaml
name: Test 

on: [push]

jobs:
  tests:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      
      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '>=1.21.0'
          check-latest: true

      - name: Run tests
        run: go test -race ./...

```

--------------------------------------------------------------------------------
/filesystemserver/inprocess_test.go:
--------------------------------------------------------------------------------

```go
package filesystemserver_test

import (
	"testing"

	"github.com/mark3labs/mcp-filesystem-server/filesystemserver"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestInProcess(t *testing.T) {
	fss, err := filesystemserver.NewFilesystemServer([]string{"."})
	require.NoError(t, err)

	mcpClient := startTestClient(t, fss)

	// just check for a specific tool
	tool := getTool(t, mcpClient, "read_file")
	assert.NotNil(t, tool, "read_file tool not found in the list of tools")
}

```

--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------

```go
package main

import (
	"fmt"
	"log"
	"os"

	"github.com/mark3labs/mcp-filesystem-server/filesystemserver"
	"github.com/mark3labs/mcp-go/server"
)

func main() {
	// Parse command line arguments
	if len(os.Args) < 2 {
		fmt.Fprintf(
			os.Stderr,
			"Usage: %s <allowed-directory> [additional-directories...]\n",
			os.Args[0],
		)
		os.Exit(1)
	}

	// Create and start the server
	fss, err := filesystemserver.NewFilesystemServer(os.Args[1:])
	if err != nil {
		log.Fatalf("Failed to create server: %v", err)
	}

	// Serve requests
	if err := server.ServeStdio(fss); err != nil {
		log.Fatalf("Server error: %v", err)
	}
}

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
FROM golang:1.23-alpine AS builder

WORKDIR /app

# Copy go.mod and go.sum first for caching dependencies
COPY go.mod go.sum ./

# Download dependencies
RUN go mod download

# Copy the source code
COPY . .

# Build the application
RUN go build -ldflags="-s -w" -o server .

FROM alpine:latest

WORKDIR /app

# Copy the built binary from the builder stage
COPY --from=builder /app/server ./

# The container will by default pass '/app' as the allowed directory if no other command line arguments are provided
ENTRYPOINT ["./server"]
CMD ["/app"]

```

--------------------------------------------------------------------------------
/filesystemserver/server_test.go:
--------------------------------------------------------------------------------

```go
package filesystemserver_test

import (
	"testing"

	"github.com/mark3labs/mcp-filesystem-server/filesystemserver"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// regression test for invalid schema => missing items in array definition
func TestReadMultipleFilesSchema(t *testing.T) {
	fsserver, err := filesystemserver.NewFilesystemServer([]string{t.TempDir()})
	require.NoError(t, err)

	mcpClient := startTestClient(t, fsserver)

	tool := getTool(t, mcpClient, "read_multiple_files")
	require.NotNil(t, tool)

	// make sure that the tool has the correct schema
	paths, ok := tool.InputSchema.Properties["paths"]
	assert.True(t, ok)
	pathsMap, ok := paths.(map[string]any)
	assert.True(t, ok)
	_, ok = pathsMap["items"]
	assert.True(t, ok)
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/handler_test.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/require"
)

// resolveAllowedDirs generates a list of allowed paths, including their resolved symlinks.
// This ensures both the original paths and their symlink-resolved counterparts are included,
// which is useful when paths may be symlinks (e.g., t.TempDir() on some Unix systems).
func resolveAllowedDirs(t *testing.T, dirs ...string) []string {
	t.Helper()
	allowedDirs := make([]string, 0)
	for _, dir := range dirs {
		allowedDirs = append(allowedDirs, dir)

		resolvedPath, err := filepath.EvalSymlinks(dir)
		require.NoError(t, err, "Failed to resolve symlinks for directory: %s", dir)

		if resolvedPath != dir {
			allowedDirs = append(allowedDirs, resolvedPath)
		}
	}
	return allowedDirs
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/list_allowed_directories.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"fmt"
	"path/filepath"
	"strings"

	"github.com/mark3labs/mcp-go/mcp"
)

func (fs *FilesystemHandler) HandleListAllowedDirectories(
	ctx context.Context,
	request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
	// Remove the trailing separator for display purposes
	displayDirs := make([]string, len(fs.allowedDirs))
	for i, dir := range fs.allowedDirs {
		displayDirs[i] = strings.TrimSuffix(dir, string(filepath.Separator))
	}

	var result strings.Builder
	result.WriteString("Allowed directories:\n\n")

	for _, dir := range displayDirs {
		resourceURI := pathToResourceURI(dir)
		result.WriteString(fmt.Sprintf("%s (%s)\n", dir, resourceURI))
	}

	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: result.String(),
			},
		},
	}, nil
}
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required:
      - allowedDirectory
    properties:
      allowedDirectory:
        type: string
        description: The absolute path to an allowed directory for the filesystem
          server. For example, in the Docker container '/app' is a good default.
      additionalDirectories:
        type: array
        items:
          type: string
        description: Optional additional allowed directories.
  commandFunction:
    # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
    |-
    (config) => { const args = [config.allowedDirectory]; if (config.additionalDirectories && Array.isArray(config.additionalDirectories)) { args.push(...config.additionalDirectories); } return { command: './server', args: args }; }
  exampleConfig:
    allowedDirectory: /app
    additionalDirectories: []

```

--------------------------------------------------------------------------------
/filesystemserver/handler/handler.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"fmt"
	"os"
	"path/filepath"
)

type FilesystemHandler struct {
	allowedDirs []string
}

func NewFilesystemHandler(allowedDirs []string) (*FilesystemHandler, error) {
	// Normalize and validate directories
	normalized := make([]string, 0, len(allowedDirs))
	for _, dir := range allowedDirs {
		abs, err := filepath.Abs(dir)
		if err != nil {
			return nil, fmt.Errorf("failed to resolve path %s: %w", dir, err)
		}

		info, err := os.Stat(abs)
		if err != nil {
			return nil, fmt.Errorf(
				"failed to access directory %s: %w",
				abs,
				err,
			)
		}
		if !info.IsDir() {
			return nil, fmt.Errorf("path is not a directory: %s", abs)
		}

		// Ensure the path ends with a separator to prevent prefix matching issues
		// For example, /tmp/foo should not match /tmp/foobar
		normalized = append(normalized, filepath.Clean(abs)+string(filepath.Separator))
	}
	return &FilesystemHandler{
		allowedDirs: normalized,
	}, nil
}

// pathToResourceURI converts a file path to a resource URI
func pathToResourceURI(path string) string {
	return "file://" + path
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/types.go:
--------------------------------------------------------------------------------

```go
package handler

import "time"

const (
	// Maximum size for inline content (5MB)
	MAX_INLINE_SIZE = 5 * 1024 * 1024
	// Maximum size for base64 encoding (1MB)
	MAX_BASE64_SIZE = 1 * 1024 * 1024
	// Maximum number of search results to return (prevent excessive output)
	MAX_SEARCH_RESULTS = 1000
	// Maximum file size in bytes to search within (10MB)
	MAX_SEARCHABLE_SIZE = 10 * 1024 * 1024
)

type FileInfo struct {
	Size        int64     `json:"size"`
	Created     time.Time `json:"created"`
	Modified    time.Time `json:"modified"`
	Accessed    time.Time `json:"accessed"`
	IsDirectory bool      `json:"isDirectory"`
	IsFile      bool      `json:"isFile"`
	Permissions string    `json:"permissions"`
}

// FileNode represents a node in the file tree
type FileNode struct {
	Name     string      `json:"name"`
	Path     string      `json:"path"`
	Type     string      `json:"type"` // "file" or "directory"
	Size     int64       `json:"size,omitempty"`
	Modified time.Time   `json:"modified,omitempty"`
	Children []*FileNode `json:"children,omitempty"`
}

// SearchResult represents a single match in a file
type SearchResult struct {
	FilePath    string
	LineNumber  int
	LineContent string
	ResourceURI string
}

```

--------------------------------------------------------------------------------
/filesystemserver/utils_test.go:
--------------------------------------------------------------------------------

```go
package filesystemserver_test

import (
	"context"
	"testing"

	"github.com/mark3labs/mcp-filesystem-server/filesystemserver"
	"github.com/mark3labs/mcp-go/client"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func startTestClient(t *testing.T, fss *server.MCPServer) client.MCPClient {
	t.Helper()

	mcpClient, err := client.NewInProcessClient(fss)
	require.NoError(t, err)
	t.Cleanup(func() { mcpClient.Close() })

	err = mcpClient.Start(context.Background())
	require.NoError(t, err)

	// Initialize the client
	initRequest := mcp.InitializeRequest{}
	initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
	initRequest.Params.ClientInfo = mcp.Implementation{
		Name:    "test-client",
		Version: "1.0.0",
	}
	result, err := mcpClient.Initialize(context.Background(), initRequest)
	require.NoError(t, err)
	assert.Equal(t, "secure-filesystem-server", result.ServerInfo.Name)
	assert.Equal(t, filesystemserver.Version, result.ServerInfo.Version)

	return mcpClient
}

func getTool(t *testing.T, mcpClient client.MCPClient, toolName string) *mcp.Tool {
	result, err := mcpClient.ListTools(context.Background(), mcp.ListToolsRequest{})
	require.NoError(t, err)
	for _, tool := range result.Tools {
		if tool.Name == toolName {
			return &tool
		}
	}
	require.Fail(t, "Tool not found", toolName)
	return nil
}

```

--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------

```yaml
name: Release

on:
  push:
    tags:
      - 'v*'

permissions:
  contents: write
  packages: write

jobs:
  goreleaser:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      
      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '>=1.21.0'
          check-latest: true
      
      - name: Run GoReleaser
        uses: goreleaser/goreleaser-action@v6
        with:
          distribution: goreleaser
          version: '~> v2'
          args: release --clean
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Extract version
        id: get-version
        run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
      
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      
      - name: Build and push Docker image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          platforms: linux/amd64,linux/arm64
          tags: |
            ghcr.io/${{ github.repository }}:latest
            ghcr.io/${{ github.repository }}:${{ steps.get-version.outputs.VERSION }}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/search_files_test.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"os"
	"path/filepath"
	"testing"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestSearchFiles_Pattern(t *testing.T) {

	// setting up test folder
	// tmpDir/
	// - foo/
	//   - bar.h
	//   - test.c
	// - test.h
	// - test.c

	dir := t.TempDir()
	test_h := filepath.Join(dir, "test.h")
	err := os.WriteFile(test_h, []byte("foo"), 0644)
	require.NoError(t, err)

	test_c := filepath.Join(dir, "test.c")
	err = os.WriteFile(test_c, []byte("foo"), 0644)
	require.NoError(t, err)

	fooDir := filepath.Join(dir, "foo")
	err = os.MkdirAll(fooDir, 0755)
	require.NoError(t, err)

	foo_bar_h := filepath.Join(fooDir, "bar.h")
	err = os.WriteFile(foo_bar_h, []byte("foo"), 0644)
	require.NoError(t, err)

	foo_test_c := filepath.Join(fooDir, "test.c")
	err = os.WriteFile(foo_test_c, []byte("foo"), 0644)
	require.NoError(t, err)

	handler, err := NewFilesystemHandler(resolveAllowedDirs(t, dir))
	require.NoError(t, err)

	tests := []struct {
		info    string
		pattern string
		matches []string
	}{
		{info: "use placeholder with extension", pattern: "*.h", matches: []string{test_h, foo_bar_h}},
		{info: "use placeholder with name", pattern: "test.*", matches: []string{test_h, test_c}},
		{info: "same filename", pattern: "test.c", matches: []string{test_c, foo_test_c}},
	}

	for _, test := range tests {
		t.Run(test.info, func(t *testing.T) {
			request := mcp.CallToolRequest{}
			request.Params.Name = "search_files"
			request.Params.Arguments = map[string]any{
				"path":    dir,
				"pattern": test.pattern,
			}

			result, err := handler.HandleSearchFiles(context.Background(), request)
			require.NoError(t, err)
			assert.False(t, result.IsError)
			assert.Len(t, result.Content, 1)

			for _, match := range test.matches {
				assert.Contains(t, result.Content[0].(mcp.TextContent).Text, match)
			}
		})
	}
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/read_file_test.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"testing"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestReadfile_Valid(t *testing.T) {
	// prepare temp directory
	dir := t.TempDir()
	content := "test-content"
	err := os.WriteFile(filepath.Join(dir, "test"), []byte(content), 0644)
	require.NoError(t, err)

	handler, err := NewFilesystemHandler(resolveAllowedDirs(t, dir))
	require.NoError(t, err)
	request := mcp.CallToolRequest{}
	request.Params.Name = "read_file"
	request.Params.Arguments = map[string]any{
		"path": filepath.Join(dir, "test"),
	}

	result, err := handler.HandleReadFile(context.Background(), request)
	require.NoError(t, err)
	assert.Len(t, result.Content, 1)
	assert.Equal(t, content, result.Content[0].(mcp.TextContent).Text)
}

func TestReadfile_Invalid(t *testing.T) {
	dir := t.TempDir()
	handler, err := NewFilesystemHandler(resolveAllowedDirs(t, dir))
	require.NoError(t, err)

	request := mcp.CallToolRequest{}
	request.Params.Name = "read_file"
	request.Params.Arguments = map[string]any{
		"path": filepath.Join(dir, "test"),
	}

	result, err := handler.HandleReadFile(context.Background(), request)
	require.NoError(t, err)
	assert.True(t, result.IsError)
	assert.Contains(t, fmt.Sprint(result.Content[0]), "no such file or directory")
}

func TestReadfile_NoAccess(t *testing.T) {
	dir1 := t.TempDir()
	dir2 := t.TempDir()

	handler, err := NewFilesystemHandler(resolveAllowedDirs(t, dir1))
	require.NoError(t, err)

	request := mcp.CallToolRequest{}
	request.Params.Name = "read_file"
	request.Params.Arguments = map[string]any{
		"path": filepath.Join(dir2, "test"),
	}

	result, err := handler.HandleReadFile(context.Background(), request)
	require.NoError(t, err)
	assert.True(t, result.IsError)
	assert.Contains(t, fmt.Sprint(result.Content[0]), "access denied - path outside allowed directories")
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/list_allowed_directories_test.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"testing"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestHandleListAllowedDirectories(t *testing.T) {
	// Setup multiple temporary directories for the test
	tmpDir1 := t.TempDir()
	tmpDir2 := t.TempDir()

	// Create a handler with multiple allowed directories
	allowedDirs := resolveAllowedDirs(t, tmpDir1, tmpDir2)
	fsHandler, err := NewFilesystemHandler(allowedDirs)
	require.NoError(t, err)

	ctx := context.Background()

	t.Run("list allowed directories", func(t *testing.T) {
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{},
			},
		}

		res, err := fsHandler.HandleListAllowedDirectories(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Verify the response contains the allowed directories
		require.Len(t, res.Content, 1)
		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "Allowed directories:")
		assert.Contains(t, textContent.Text, tmpDir1)
		assert.Contains(t, textContent.Text, tmpDir2)
		assert.Contains(t, textContent.Text, "file://")
	})

	t.Run("single allowed directory", func(t *testing.T) {
		singleDir := t.TempDir()
		singleAllowedDirs := resolveAllowedDirs(t, singleDir)
		singleFsHandler, err := NewFilesystemHandler(singleAllowedDirs)
		require.NoError(t, err)

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{},
			},
		}

		res, err := singleFsHandler.HandleListAllowedDirectories(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Verify the response contains the single allowed directory
		require.Len(t, res.Content, 1)
		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "Allowed directories:")
		assert.Contains(t, textContent.Text, singleDir)
		assert.Contains(t, textContent.Text, "file://")
	})
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/create_directory_test.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"os"
	"path/filepath"
	"testing"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestHandleCreateDirectory(t *testing.T) {
	// Setup a temporary directory for the test
	tmpDir := t.TempDir()

	// Create a handler with the temp dir as an allowed path
	allowedDirs := resolveAllowedDirs(t, tmpDir)
	fsHandler, err := NewFilesystemHandler(allowedDirs)
	require.NoError(t, err)

	ctx := context.Background()

	t.Run("create a new directory", func(t *testing.T) {
		newDirPath := filepath.Join(tmpDir, "new_directory")
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": newDirPath,
				},
			},
		}

		res, err := fsHandler.HandleCreateDirectory(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Verify the directory was created
		info, err := os.Stat(newDirPath)
		require.NoError(t, err)
		assert.True(t, info.IsDir())
	})

	t.Run("directory already exists", func(t *testing.T) {
		existingDirPath := filepath.Join(tmpDir, "existing_directory")
		err := os.Mkdir(existingDirPath, 0755)
		require.NoError(t, err)

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": existingDirPath,
				},
			},
		}

		res, err := fsHandler.HandleCreateDirectory(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError) // Should not be an error, just a message that it already exists
	})

	t.Run("path exists but is not a directory", func(t *testing.T) {
		filePath := filepath.Join(tmpDir, "existing_file.txt")
		err := os.WriteFile(filePath, []byte("content"), 0644)
		require.NoError(t, err)

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": filePath,
				},
			},
		}

		res, err := fsHandler.HandleCreateDirectory(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)
	})

	t.Run("path is in a non-allowed directory", func(t *testing.T) {
		otherDir := t.TempDir()

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": filepath.Join(otherDir, "new_directory"),
				},
			},
		}

		res, err := fsHandler.HandleCreateDirectory(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)
	})
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/create_directory.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"fmt"
	"os"

	"github.com/mark3labs/mcp-go/mcp"
)

func (fs *FilesystemHandler) HandleCreateDirectory(
	ctx context.Context,
	request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
	path, err := request.RequireString("path")
	if err != nil {
		return nil, err
	}

	// Handle empty or relative paths like "." or "./" by converting to absolute path
	if path == "." || path == "./" {
		// Get current working directory
		cwd, err := os.Getwd()
		if err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error resolving current directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
		path = cwd
	}

	validPath, err := fs.validatePath(path)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Check if path already exists
	if info, err := os.Stat(validPath); err == nil {
		if info.IsDir() {
			resourceURI := pathToResourceURI(validPath)
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Directory already exists: %s", path),
					},
					mcp.EmbeddedResource{
						Type: "resource",
						Resource: mcp.TextResourceContents{
							URI:      resourceURI,
							MIMEType: "text/plain",
							Text:     fmt.Sprintf("Directory: %s", validPath),
						},
					},
				},
			}, nil
		}
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: Path exists but is not a directory: %s", path),
				},
			},
			IsError: true,
		}, nil
	}

	if err := os.MkdirAll(validPath, 0755); err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error creating directory: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	resourceURI := pathToResourceURI(validPath)
	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: fmt.Sprintf("Successfully created directory %s", path),
			},
			mcp.EmbeddedResource{
				Type: "resource",
				Resource: mcp.TextResourceContents{
					URI:      resourceURI,
					MIMEType: "text/plain",
					Text:     fmt.Sprintf("Directory: %s", validPath),
				},
			},
		},
	}, nil
}
```

--------------------------------------------------------------------------------
/filesystemserver/handler/write_file.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/mark3labs/mcp-go/mcp"
)

func (fs *FilesystemHandler) HandleWriteFile(
	ctx context.Context,
	request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
	path, err := request.RequireString("path")
	if err != nil {
		return nil, err
	}
	content, err := request.RequireString("content")
	if err != nil {
		return nil, err
	}

	// Handle empty or relative paths like "." or "./" by converting to absolute path
	if path == "." || path == "./" {
		// Get current working directory
		cwd, err := os.Getwd()
		if err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error resolving current directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
		path = cwd
	}

	validPath, err := fs.validatePath(path)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Check if it's a directory
	if info, err := os.Stat(validPath); err == nil && info.IsDir() {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: "Error: Cannot write to a directory",
				},
			},
			IsError: true,
		}, nil
	}

	// Create parent directories if they don't exist
	parentDir := filepath.Dir(validPath)
	if err := os.MkdirAll(parentDir, 0755); err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error creating parent directories: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	if err := os.WriteFile(validPath, []byte(content), 0644); err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error writing file: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Get file info for the response
	info, err := os.Stat(validPath)
	if err != nil {
		// File was written but we couldn't get info
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Successfully wrote to %s", path),
				},
			},
		}, nil
	}

	resourceURI := pathToResourceURI(validPath)
	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: fmt.Sprintf("Successfully wrote %d bytes to %s", info.Size(), path),
			},
			mcp.EmbeddedResource{
				Type: "resource",
				Resource: mcp.TextResourceContents{
					URI:      resourceURI,
					MIMEType: "text/plain",
					Text:     fmt.Sprintf("File: %s (%d bytes)", validPath, info.Size()),
				},
			},
		},
	}, nil
}
```

--------------------------------------------------------------------------------
/filesystemserver/handler/list_directory.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/mark3labs/mcp-go/mcp"
)

func (fs *FilesystemHandler) HandleListDirectory(
	ctx context.Context,
	request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
	path, err := request.RequireString("path")
	if err != nil {
		return nil, err
	}

	// Handle empty or relative paths like "." or "./" by converting to absolute path
	if path == "." || path == "./" {
		// Get current working directory
		cwd, err := os.Getwd()
		if err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error resolving current directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
		path = cwd
	}

	validPath, err := fs.validatePath(path)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Check if it's a directory
	info, err := os.Stat(validPath)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	if !info.IsDir() {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: "Error: Path is not a directory",
				},
			},
			IsError: true,
		}, nil
	}

	entries, err := os.ReadDir(validPath)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error reading directory: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	var result strings.Builder
	result.WriteString(fmt.Sprintf("Directory listing for: %s\n\n", validPath))

	for _, entry := range entries {
		entryPath := filepath.Join(validPath, entry.Name())
		resourceURI := pathToResourceURI(entryPath)

		if entry.IsDir() {
			result.WriteString(fmt.Sprintf("[DIR]  %s (%s)\n", entry.Name(), resourceURI))
		} else {
			info, err := entry.Info()
			if err == nil {
				result.WriteString(fmt.Sprintf("[FILE] %s (%s) - %d bytes\n",
					entry.Name(), resourceURI, info.Size()))
			} else {
				result.WriteString(fmt.Sprintf("[FILE] %s (%s)\n", entry.Name(), resourceURI))
			}
		}
	}

	// Return both text content and embedded resource
	resourceURI := pathToResourceURI(validPath)
	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: result.String(),
			},
			mcp.EmbeddedResource{
				Type: "resource",
				Resource: mcp.TextResourceContents{
					URI:      resourceURI,
					MIMEType: "text/plain",
					Text:     fmt.Sprintf("Directory: %s", validPath),
				},
			},
		},
	}, nil
}
```

--------------------------------------------------------------------------------
/filesystemserver/handler/delete_file.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"fmt"
	"os"

	"github.com/mark3labs/mcp-go/mcp"
)

func (fs *FilesystemHandler) HandleDeleteFile(
	ctx context.Context,
	request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
	path, err := request.RequireString("path")
	if err != nil {
		return nil, err
	}

	// Handle empty or relative paths like "." or "./" by converting to absolute path
	if path == "." || path == "./" {
		// Get current working directory
		cwd, err := os.Getwd()
		if err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error resolving current directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
		path = cwd
	}

	validPath, err := fs.validatePath(path)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Check if path exists
	info, err := os.Stat(validPath)
	if os.IsNotExist(err) {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: Path does not exist: %s", path),
				},
			},
			IsError: true,
		}, nil
	} else if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error accessing path: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Extract recursive parameter (optional, default: false)
	recursive := false
	if recursiveParam, err := request.RequireBool("recursive"); err == nil {
		recursive = recursiveParam
	}

	// Check if it's a directory and handle accordingly
	if info.IsDir() {
		if !recursive {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error: %s is a directory. Use recursive=true to delete directories.", path),
					},
				},
				IsError: true,
			}, nil
		}

		// It's a directory and recursive is true, so remove it
		if err := os.RemoveAll(validPath); err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error deleting directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}

		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Successfully deleted directory %s", path),
				},
			},
		}, nil
	}

	// It's a file, delete it
	if err := os.Remove(validPath); err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error deleting file: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: fmt.Sprintf("Successfully deleted file %s", path),
			},
		},
	}, nil
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/get_file_info.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/djherbis/times"
	"github.com/mark3labs/mcp-go/mcp"
)

func (fs *FilesystemHandler) HandleGetFileInfo(
	ctx context.Context,
	request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
	path, err := request.RequireString("path")
	if err != nil {
		return nil, err
	}

	// Handle empty or relative paths like "." or "./" by converting to absolute path
	if path == "." || path == "./" {
		// Get current working directory
		cwd, err := os.Getwd()
		if err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error resolving current directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
		path = cwd
	}

	validPath, err := fs.validatePath(path)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	info, err := fs.getFileStats(validPath)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error getting file info: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Get MIME type for files
	mimeType := "directory"
	if info.IsFile {
		mimeType = detectMimeType(validPath)
	}

	resourceURI := pathToResourceURI(validPath)

	// Determine file type text
	var fileTypeText string
	if info.IsDirectory {
		fileTypeText = "Directory"
	} else {
		fileTypeText = "File"
	}

	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: fmt.Sprintf(
					"File information for: %s\n\nSize: %d bytes\nCreated: %s\nModified: %s\nAccessed: %s\nIsDirectory: %v\nIsFile: %v\nPermissions: %s\nMIME Type: %s\nResource URI: %s",
					validPath,
					info.Size,
					info.Created.Format(time.RFC3339),
					info.Modified.Format(time.RFC3339),
					info.Accessed.Format(time.RFC3339),
					info.IsDirectory,
					info.IsFile,
					info.Permissions,
					mimeType,
					resourceURI,
				),
			},
			mcp.EmbeddedResource{
				Type: "resource",
				Resource: mcp.TextResourceContents{
					URI:      resourceURI,
					MIMEType: "text/plain",
					Text: fmt.Sprintf("%s: %s (%s, %d bytes)",
						fileTypeText,
						validPath,
						mimeType,
						info.Size),
				},
			},
		},
	}, nil
}

func (fs *FilesystemHandler) getFileStats(path string) (FileInfo, error) {
	info, err := os.Stat(path)
	if err != nil {
		return FileInfo{}, err
	}

	timespec, err := times.Stat(path)
	if err != nil {
		return FileInfo{}, fmt.Errorf("failed to get file times: %w", err)
	}

	createdTime := time.Time{}
	if timespec.HasBirthTime() {
		createdTime = timespec.BirthTime()
	}

	return FileInfo{
		Size:        info.Size(),
		Created:     createdTime,
		Modified:    timespec.ModTime(),
		Accessed:    timespec.AccessTime(),
		IsDirectory: info.IsDir(),
		IsFile:      !info.IsDir(),
		Permissions: fmt.Sprintf("%o", info.Mode().Perm()),
	}, nil
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/get_file_info_test.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"os"
	"path/filepath"
	"testing"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestHandleGetFileInfo(t *testing.T) {
	// Setup a temporary directory for the test
	tmpDir := t.TempDir()

	// Create a handler with the temp dir as an allowed path
	allowedDirs := resolveAllowedDirs(t, tmpDir)
	fsHandler, err := NewFilesystemHandler(allowedDirs)
	require.NoError(t, err)

	ctx := context.Background()

	t.Run("get file info for a file", func(t *testing.T) {
		filePath := filepath.Join(tmpDir, "test_file.txt")
		fileContent := "Hello, world!"
		err := os.WriteFile(filePath, []byte(fileContent), 0644)
		require.NoError(t, err)

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": filePath,
				},
			},
		}

		res, err := fsHandler.HandleGetFileInfo(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Verify the response contains file information
		require.Len(t, res.Content, 2)
		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "File information for:")
		assert.Contains(t, textContent.Text, filePath)
		assert.Contains(t, textContent.Text, "IsFile: true")
		assert.Contains(t, textContent.Text, "IsDirectory: false")
		assert.Contains(t, textContent.Text, "Size: 13 bytes") // Length of "Hello, world!"
	})

	t.Run("get file info for a directory", func(t *testing.T) {
		dirPath := filepath.Join(tmpDir, "test_directory")
		err := os.Mkdir(dirPath, 0755)
		require.NoError(t, err)

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": dirPath,
				},
			},
		}

		res, err := fsHandler.HandleGetFileInfo(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Verify the response contains directory information
		require.Len(t, res.Content, 2)
		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "File information for:")
		assert.Contains(t, textContent.Text, dirPath)
		assert.Contains(t, textContent.Text, "IsFile: false")
		assert.Contains(t, textContent.Text, "IsDirectory: true")
		assert.Contains(t, textContent.Text, "MIME Type: directory")
	})

	t.Run("file does not exist", func(t *testing.T) {
		nonExistentPath := filepath.Join(tmpDir, "non_existent_file.txt")

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": nonExistentPath,
				},
			},
		}

		res, err := fsHandler.HandleGetFileInfo(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)
	})

	t.Run("path is in a non-allowed directory", func(t *testing.T) {
		otherDir := t.TempDir()

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": filepath.Join(otherDir, "some_file.txt"),
				},
			},
		}

		res, err := fsHandler.HandleGetFileInfo(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)
	})
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/resources.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"encoding/base64"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/mark3labs/mcp-go/mcp"
)

// HandleReadResource handles the MCP resource reading functionality
func (fs *FilesystemHandler) HandleReadResource(
	ctx context.Context,
	request mcp.ReadResourceRequest,
) ([]mcp.ResourceContents, error) {
	uri := request.Params.URI

	// Check if it's a file:// URI
	if !strings.HasPrefix(uri, "file://") {
		return nil, fmt.Errorf("unsupported URI scheme: %s", uri)
	}

	// Extract the path from the URI
	path := strings.TrimPrefix(uri, "file://")

	// Validate the path
	validPath, err := fs.validatePath(path)
	if err != nil {
		return nil, err
	}

	// Get file info
	fileInfo, err := os.Stat(validPath)
	if err != nil {
		return nil, err
	}

	// If it's a directory, return a listing
	if fileInfo.IsDir() {
		entries, err := os.ReadDir(validPath)
		if err != nil {
			return nil, err
		}

		var result strings.Builder
		result.WriteString(fmt.Sprintf("Directory listing for: %s\n\n", validPath))

		for _, entry := range entries {
			entryPath := filepath.Join(validPath, entry.Name())
			entryURI := pathToResourceURI(entryPath)

			if entry.IsDir() {
				result.WriteString(fmt.Sprintf("[DIR]  %s (%s)\n", entry.Name(), entryURI))
			} else {
				info, err := entry.Info()
				if err == nil {
					result.WriteString(fmt.Sprintf("[FILE] %s (%s) - %d bytes\n",
						entry.Name(), entryURI, info.Size()))
				} else {
					result.WriteString(fmt.Sprintf("[FILE] %s (%s)\n", entry.Name(), entryURI))
				}
			}
		}

		return []mcp.ResourceContents{
			mcp.TextResourceContents{
				URI:      uri,
				MIMEType: "text/plain",
				Text:     result.String(),
			},
		}, nil
	}

	// It's a file, determine how to handle it
	mimeType := detectMimeType(validPath)

	// Check file size
	if fileInfo.Size() > MAX_INLINE_SIZE {
		// File is too large to inline, return a reference instead
		return []mcp.ResourceContents{
			mcp.TextResourceContents{
				URI:      uri,
				MIMEType: "text/plain",
				Text:     fmt.Sprintf("File is too large to display inline (%d bytes). Use the read_file tool to access specific portions.", fileInfo.Size()),
			},
		}, nil
	}

	// Read the file content
	content, err := os.ReadFile(validPath)
	if err != nil {
		return nil, err
	}

	// Handle based on content type
	if isTextFile(mimeType) {
		// It's a text file, return as text
		return []mcp.ResourceContents{
			mcp.TextResourceContents{
				URI:      uri,
				MIMEType: mimeType,
				Text:     string(content),
			},
		}, nil
	} else {
		// It's a binary file
		if fileInfo.Size() <= MAX_BASE64_SIZE {
			// Small enough for base64 encoding
			return []mcp.ResourceContents{
				mcp.BlobResourceContents{
					URI:      uri,
					MIMEType: mimeType,
					Blob:     base64.StdEncoding.EncodeToString(content),
				},
			}, nil
		} else {
			// Too large for base64, return a reference
			return []mcp.ResourceContents{
				mcp.TextResourceContents{
					URI:      uri,
					MIMEType: "text/plain",
					Text:     fmt.Sprintf("Binary file (%s, %d bytes). Use the read_file tool to access specific portions.", mimeType, fileInfo.Size()),
				},
			}, nil
		}
	}
}
```

--------------------------------------------------------------------------------
/filesystemserver/handler/copy_file_test.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"os"
	"path/filepath"
	"testing"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestHandleCopyFile(t *testing.T) {
	// Setup a temporary directory for the test
	tmpDir := t.TempDir()

	// Create a handler with the temp dir as an allowed path
	allowedDirs := resolveAllowedDirs(t, tmpDir)
	fsHandler, err := NewFilesystemHandler(allowedDirs)
	require.NoError(t, err)

	ctx := context.Background()

	// Create a source file
	sourceFilePath := filepath.Join(tmpDir, "source.txt")
	err = os.WriteFile(sourceFilePath, []byte("hello world"), 0644)
	require.NoError(t, err)

	// Create a source directory
	sourceDirPath := filepath.Join(tmpDir, "source_dir")
	err = os.Mkdir(sourceDirPath, 0755)
	require.NoError(t, err)

	// Create a file inside the source directory
	nestedFilePath := filepath.Join(sourceDirPath, "nested.txt")
	err = os.WriteFile(nestedFilePath, []byte("nested hello"), 0644)
	require.NoError(t, err)

	t.Run("copy a single file", func(t *testing.T) {
		destinationPath := filepath.Join(tmpDir, "destination.txt")
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"source":      sourceFilePath,
					"destination": destinationPath,
				},
			},
		}

		res, err := fsHandler.HandleCopyFile(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Verify the file was copied
		_, err = os.Stat(destinationPath)
		require.NoError(t, err)

		content, err := os.ReadFile(destinationPath)
		require.NoError(t, err)
		assert.Equal(t, "hello world", string(content))
	})

	t.Run("copy a directory", func(t *testing.T) {
		destinationPath := filepath.Join(tmpDir, "destination_dir")
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"source":      sourceDirPath,
					"destination": destinationPath,
				},
			},
		}

		res, err := fsHandler.HandleCopyFile(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Verify the directory was copied
		_, err = os.Stat(destinationPath)
		require.NoError(t, err)

		// Verify the nested file was copied
		nestedDestPath := filepath.Join(destinationPath, "nested.txt")
		_, err = os.Stat(nestedDestPath)
		require.NoError(t, err)

		content, err := os.ReadFile(nestedDestPath)
		require.NoError(t, err)
		assert.Equal(t, "nested hello", string(content))
	})

	t.Run("source does not exist", func(t *testing.T) {
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"source":      filepath.Join(tmpDir, "non-existent-file.txt"),
					"destination": filepath.Join(tmpDir, "destination.txt"),
				},
			},
		}

		res, err := fsHandler.HandleCopyFile(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)
	})

	t.Run("destination is in a non-allowed directory", func(t *testing.T) {
		// Setup a temporary directory for the test
		otherDir := t.TempDir()

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"source":      sourceFilePath,
					"destination": filepath.Join(otherDir, "destination.txt"),
				},
			},
		}

		res, err := fsHandler.HandleCopyFile(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)
	})
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/search_files.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/gobwas/glob"
	"github.com/mark3labs/mcp-go/mcp"
)

func (fs *FilesystemHandler) HandleSearchFiles(
	ctx context.Context,
	request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
	path, err := request.RequireString("path")
	if err != nil {
		return nil, err
	}
	pattern, err := request.RequireString("pattern")
	if err != nil {
		return nil, err
	}

	// Handle empty or relative paths like "." or "./" by converting to absolute path
	if path == "." || path == "./" {
		// Get current working directory
		cwd, err := os.Getwd()
		if err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error resolving current directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
		path = cwd
	}

	validPath, err := fs.validatePath(path)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Check if it's a directory
	info, err := os.Stat(validPath)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	if !info.IsDir() {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: "Error: Search path must be a directory",
				},
			},
			IsError: true,
		}, nil
	}

	results, err := searchFiles(validPath, pattern, fs)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error searching files: %v",
						err),
				},
			},
			IsError: true,
		}, nil
	}

	if len(results) == 0 {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("No files found matching pattern '%s' in %s", pattern, path),
				},
			},
		}, nil
	}

	// Format results with resource URIs
	var formattedResults strings.Builder
	formattedResults.WriteString(fmt.Sprintf("Found %d results:\n\n", len(results)))

	for _, result := range results {
		resourceURI := pathToResourceURI(result)
		info, err := os.Stat(result)
		if err == nil {
			if info.IsDir() {
				formattedResults.WriteString(fmt.Sprintf("[DIR]  %s (%s)\n", result, resourceURI))
			} else {
				formattedResults.WriteString(fmt.Sprintf("[FILE] %s (%s) - %d bytes\n",
					result, resourceURI, info.Size()))
			}
		} else {
			formattedResults.WriteString(fmt.Sprintf("%s (%s)\n", result, resourceURI))
		}
	}

	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: formattedResults.String(),
			},
		},
	}, nil
}

func searchFiles(rootPath, pattern string, fs *FilesystemHandler) ([]string, error) {
	var results []string
	globPattern := glob.MustCompile(pattern)

	err := filepath.Walk(
		rootPath,
		func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return nil // Skip errors and continue
			}

			// Try to validate path
			if _, err := fs.validatePath(path); err != nil {
				return nil // Skip invalid paths
			}

			if globPattern.Match(info.Name()) {
				results = append(results, path)
			}
			return nil
		},
	)
	if err != nil {
		return nil, err
	}
	return results, nil
}
```

--------------------------------------------------------------------------------
/filesystemserver/handler/move_file.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"fmt"
	"os"
	"path/filepath"

	"github.com/mark3labs/mcp-go/mcp"
)

func (fs *FilesystemHandler) HandleMoveFile(
	ctx context.Context,
	request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
	source, err := request.RequireString("source")
	if err != nil {
		return nil, err
	}
	destination, err := request.RequireString("destination")
	if err != nil {
		return nil, err
	}

	// Handle empty or relative paths for source
	if source == "." || source == "./" {
		// Get current working directory
		cwd, err := os.Getwd()
		if err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error resolving current directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
		source = cwd
	}

	// Handle empty or relative paths for destination
	if destination == "." || destination == "./" {
		// Get current working directory
		cwd, err := os.Getwd()
		if err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error resolving current directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
		destination = cwd
	}

	validSource, err := fs.validatePath(source)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error with source path: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Check if source exists
	if _, err := os.Stat(validSource); os.IsNotExist(err) {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: Source does not exist: %s", source),
				},
			},
			IsError: true,
		}, nil
	}

	// For destination path, validate the parent directory first and create it if needed
	destDir := filepath.Dir(destination)
	validDestDir, err := fs.validatePath(destDir)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error with destination directory path: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Create parent directory for destination if it doesn't exist
	if err := os.MkdirAll(validDestDir, 0755); err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error creating destination directory: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Now validate the full destination path
	validDest, err := fs.validatePath(destination)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error with destination path: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	if err := os.Rename(validSource, validDest); err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error moving file: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	resourceURI := pathToResourceURI(validDest)
	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: fmt.Sprintf(
					"Successfully moved %s to %s",
					source,
					destination,
				),
			},
			mcp.EmbeddedResource{
				Type: "resource",
				Resource: mcp.TextResourceContents{
					URI:      resourceURI,
					MIMEType: "text/plain",
					Text:     fmt.Sprintf("Moved file: %s", validDest),
				},
			},
		},
	}, nil
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/list_directory_test.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"os"
	"path/filepath"
	"testing"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestHandleListDirectory(t *testing.T) {
	// Setup a temporary directory for the test
	tmpDir := t.TempDir()

	// Create a handler with the temp dir as an allowed path
	allowedDirs := resolveAllowedDirs(t, tmpDir)
	fsHandler, err := NewFilesystemHandler(allowedDirs)
	require.NoError(t, err)

	ctx := context.Background()

	// Create test directory structure
	subDir := filepath.Join(tmpDir, "subdirectory")
	err = os.Mkdir(subDir, 0755)
	require.NoError(t, err)

	testFile := filepath.Join(tmpDir, "test_file.txt")
	err = os.WriteFile(testFile, []byte("hello world"), 0644)
	require.NoError(t, err)

	t.Run("list directory with files and subdirectories", func(t *testing.T) {
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": tmpDir,
				},
			},
		}

		res, err := fsHandler.HandleListDirectory(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Verify the response contains directory listing
		require.Len(t, res.Content, 2)
		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "Directory listing for:")
		assert.Contains(t, textContent.Text, tmpDir)
		assert.Contains(t, textContent.Text, "[DIR]  subdirectory")
		assert.Contains(t, textContent.Text, "[FILE] test_file.txt")
		assert.Contains(t, textContent.Text, "11 bytes") // Length of "hello world"
		assert.Contains(t, textContent.Text, "file://")

		// Verify embedded resource
		embeddedResource := res.Content[1].(mcp.EmbeddedResource)
		assert.Equal(t, "resource", embeddedResource.Type)
	})

	t.Run("list empty directory", func(t *testing.T) {
		emptyDir := filepath.Join(tmpDir, "empty_directory")
		err := os.Mkdir(emptyDir, 0755)
		require.NoError(t, err)

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": emptyDir,
				},
			},
		}

		res, err := fsHandler.HandleListDirectory(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Verify the response contains directory listing for empty directory
		require.Len(t, res.Content, 2)
		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "Directory listing for:")
		assert.Contains(t, textContent.Text, emptyDir)
	})

	t.Run("try to list a file instead of directory", func(t *testing.T) {
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": testFile,
				},
			},
		}

		res, err := fsHandler.HandleListDirectory(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)

		// Verify error message
		require.Len(t, res.Content, 1)
		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "Path is not a directory")
	})

	t.Run("try to list non-existent directory", func(t *testing.T) {
		nonExistentPath := filepath.Join(tmpDir, "non_existent_directory")

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": nonExistentPath,
				},
			},
		}

		res, err := fsHandler.HandleListDirectory(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)
	})

	t.Run("path is in a non-allowed directory", func(t *testing.T) {
		otherDir := t.TempDir()

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": otherDir,
				},
			},
		}

		res, err := fsHandler.HandleListDirectory(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)
	})
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/helper.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"fmt"
	"mime"
	"os"
	"path/filepath"
	"slices"
	"strings"

	"github.com/gabriel-vasile/mimetype"
)

// isPathInAllowedDirs checks if a path is within any of the allowed directories
func (fs *FilesystemHandler) isPathInAllowedDirs(path string) bool {
	// Ensure path is absolute and clean
	absPath, err := filepath.Abs(path)
	if err != nil {
		return false
	}

	// Add trailing separator to ensure we're checking a directory or a file within a directory
	// and not a prefix match (e.g., /tmp/foo should not match /tmp/foobar)
	if !strings.HasSuffix(absPath, string(filepath.Separator)) {
		// If it's a file, we need to check its directory
		if info, err := os.Stat(absPath); err == nil && !info.IsDir() {
			absPath = filepath.Dir(absPath) + string(filepath.Separator)
		} else {
			absPath = absPath + string(filepath.Separator)
		}
	}

	// Check if the path is within any of the allowed directories
	for _, dir := range fs.allowedDirs {
		if strings.HasPrefix(absPath, dir) {
			return true
		}
	}
	return false
}

func (fs *FilesystemHandler) validatePath(requestedPath string) (string, error) {
	// Always convert to absolute path first
	abs, err := filepath.Abs(requestedPath)
	if err != nil {
		return "", fmt.Errorf("invalid path: %w", err)
	}

	// Check if path is within allowed directories
	if !fs.isPathInAllowedDirs(abs) {
		return "", fmt.Errorf(
			"access denied - path outside allowed directories: %s",
			abs,
		)
	}

	// Handle symlinks
	realPath, err := filepath.EvalSymlinks(abs)
	if err != nil {
		if !os.IsNotExist(err) {
			return "", err
		}
		// For new files, check parent directory
		parent := filepath.Dir(abs)
		realParent, err := filepath.EvalSymlinks(parent)
		if err != nil {
			return "", fmt.Errorf("parent directory does not exist: %s", parent)
		}

		if !fs.isPathInAllowedDirs(realParent) {
			return "", fmt.Errorf(
				"access denied - parent directory outside allowed directories",
			)
		}
		return abs, nil
	}

	// Check if the real path (after resolving symlinks) is still within allowed directories
	if !fs.isPathInAllowedDirs(realPath) {
		return "", fmt.Errorf(
			"access denied - symlink target outside allowed directories",
		)
	}

	return realPath, nil
}

// detectMimeType tries to determine the MIME type of a file
func detectMimeType(path string) string {
	// Use mimetype library for more accurate detection
	mtype, err := mimetype.DetectFile(path)
	if err != nil {
		// Fallback to extension-based detection if file can't be read
		ext := filepath.Ext(path)
		if ext != "" {
			mimeType := mime.TypeByExtension(ext)
			if mimeType != "" {
				return mimeType
			}
		}
		return "application/octet-stream" // Default
	}

	return mtype.String()
}

// isTextFile determines if a file is likely a text file based on MIME type
func isTextFile(mimeType string) bool {
	// Check for common text MIME types
	if strings.HasPrefix(mimeType, "text/") {
		return true
	}

	// Common application types that are text-based
	textApplicationTypes := []string{
		"application/json",
		"application/xml",
		"application/javascript",
		"application/x-javascript",
		"application/typescript",
		"application/x-typescript",
		"application/x-yaml",
		"application/yaml",
		"application/toml",
		"application/x-sh",
		"application/x-shellscript",
	}

	if slices.Contains(textApplicationTypes, mimeType) {
		return true
	}

	// Check for +format types
	if strings.Contains(mimeType, "+xml") ||
		strings.Contains(mimeType, "+json") ||
		strings.Contains(mimeType, "+yaml") {
		return true
	}

	// Common code file types that might be misidentified
	if strings.HasPrefix(mimeType, "text/x-") {
		return true
	}

	if strings.HasPrefix(mimeType, "application/x-") &&
		(strings.Contains(mimeType, "script") ||
			strings.Contains(mimeType, "source") ||
			strings.Contains(mimeType, "code")) {
		return true
	}

	return false
}

// isImageFile determines if a file is an image based on MIME type
func isImageFile(mimeType string) bool {
	return strings.HasPrefix(mimeType, "image/") ||
		(mimeType == "application/xml" && strings.HasSuffix(strings.ToLower(mimeType), ".svg"))
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/delete_file_test.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"os"
	"path/filepath"
	"testing"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestHandleDeleteFile(t *testing.T) {
	// Setup a temporary directory for the test
	tmpDir := t.TempDir()

	// Create a handler with the temp dir as an allowed path
	allowedDirs := resolveAllowedDirs(t, tmpDir)
	fsHandler, err := NewFilesystemHandler(allowedDirs)
	require.NoError(t, err)

	ctx := context.Background()

	t.Run("delete a file", func(t *testing.T) {
		filePath := filepath.Join(tmpDir, "test_file.txt")
		err := os.WriteFile(filePath, []byte("test content"), 0644)
		require.NoError(t, err)

		// Verify file exists before deletion
		_, err = os.Stat(filePath)
		require.NoError(t, err)

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": filePath,
				},
			},
		}

		res, err := fsHandler.HandleDeleteFile(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Verify file was deleted
		_, err = os.Stat(filePath)
		assert.True(t, os.IsNotExist(err))
	})

	t.Run("delete an empty directory with recursive=true", func(t *testing.T) {
		dirPath := filepath.Join(tmpDir, "empty_directory")
		err := os.Mkdir(dirPath, 0755)
		require.NoError(t, err)

		// Verify directory exists before deletion
		_, err = os.Stat(dirPath)
		require.NoError(t, err)

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path":      dirPath,
					"recursive": true,
				},
			},
		}

		res, err := fsHandler.HandleDeleteFile(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Verify directory was deleted
		_, err = os.Stat(dirPath)
		assert.True(t, os.IsNotExist(err))
	})

	t.Run("delete a directory with contents using recursive=true", func(t *testing.T) {
		dirPath := filepath.Join(tmpDir, "directory_with_contents")
		err := os.Mkdir(dirPath, 0755)
		require.NoError(t, err)

		// Create a file inside the directory
		filePath := filepath.Join(dirPath, "nested_file.txt")
		err = os.WriteFile(filePath, []byte("nested content"), 0644)
		require.NoError(t, err)

		// Create a subdirectory
		subDirPath := filepath.Join(dirPath, "subdirectory")
		err = os.Mkdir(subDirPath, 0755)
		require.NoError(t, err)

		// Verify directory and contents exist before deletion
		_, err = os.Stat(dirPath)
		require.NoError(t, err)
		_, err = os.Stat(filePath)
		require.NoError(t, err)
		_, err = os.Stat(subDirPath)
		require.NoError(t, err)

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path":      dirPath,
					"recursive": true,
				},
			},
		}

		res, err := fsHandler.HandleDeleteFile(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Verify directory and all contents were deleted
		_, err = os.Stat(dirPath)
		assert.True(t, os.IsNotExist(err))
	})

	t.Run("try to delete directory without recursive flag", func(t *testing.T) {
		dirPath := filepath.Join(tmpDir, "directory_no_recursive")
		err := os.Mkdir(dirPath, 0755)
		require.NoError(t, err)

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": dirPath,
				},
			},
		}

		res, err := fsHandler.HandleDeleteFile(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)

		// Verify directory still exists
		_, err = os.Stat(dirPath)
		require.NoError(t, err)
	})

	t.Run("try to delete non-existent file", func(t *testing.T) {
		nonExistentPath := filepath.Join(tmpDir, "non_existent_file.txt")

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": nonExistentPath,
				},
			},
		}

		res, err := fsHandler.HandleDeleteFile(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)
	})

	t.Run("path is in a non-allowed directory", func(t *testing.T) {
		otherDir := t.TempDir()

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": filepath.Join(otherDir, "some_file.txt"),
				},
			},
		}

		res, err := fsHandler.HandleDeleteFile(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)
	})
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/read_multiple_files.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"encoding/base64"
	"fmt"
	"os"

	"github.com/mark3labs/mcp-go/mcp"
)

func (fs *FilesystemHandler) HandleReadMultipleFiles(
	ctx context.Context,
	request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
	pathsSlice, err := request.RequireStringSlice("paths")
	if err != nil {
		return nil, err
	}

	if len(pathsSlice) == 0 {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: "No files specified to read",
				},
			},
			IsError: true,
		}, nil
	}

	// Maximum number of files to read in a single request
	const maxFiles = 50
	if len(pathsSlice) > maxFiles {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Too many files requested. Maximum is %d files per request.", maxFiles),
				},
			},
			IsError: true,
		}, nil
	}

	// Process each file
	var results []mcp.Content
	for _, path := range pathsSlice {
		// Handle empty or relative paths like "." or "./" by converting to absolute path
		if path == "." || path == "./" {
			// Get current working directory
			cwd, err := os.Getwd()
			if err != nil {
				results = append(results, mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error resolving current directory for path '%s': %v", path, err),
				})
				continue
			}
			path = cwd
		}

		validPath, err := fs.validatePath(path)
		if err != nil {
			results = append(results, mcp.TextContent{
				Type: "text",
				Text: fmt.Sprintf("Error with path '%s': %v", path, err),
			})
			continue
		}

		// Check if it's a directory
		info, err := os.Stat(validPath)
		if err != nil {
			results = append(results, mcp.TextContent{
				Type: "text",
				Text: fmt.Sprintf("Error accessing '%s': %v", path, err),
			})
			continue
		}

		if info.IsDir() {
			// For directories, return a resource reference instead
			resourceURI := pathToResourceURI(validPath)
			results = append(results, mcp.TextContent{
				Type: "text",
				Text: fmt.Sprintf("'%s' is a directory. Use list_directory tool or resource URI: %s", path, resourceURI),
			})
			continue
		}

		// Determine MIME type
		mimeType := detectMimeType(validPath)

		// Check file size
		if info.Size() > MAX_INLINE_SIZE {
			// File is too large to inline, return a resource reference
			resourceURI := pathToResourceURI(validPath)
			results = append(results, mcp.TextContent{
				Type: "text",
				Text: fmt.Sprintf("File '%s' is too large to display inline (%d bytes). Access it via resource URI: %s",
					path, info.Size(), resourceURI),
			})
			continue
		}

		// Read file content
		content, err := os.ReadFile(validPath)
		if err != nil {
			results = append(results, mcp.TextContent{
				Type: "text",
				Text: fmt.Sprintf("Error reading file '%s': %v", path, err),
			})
			continue
		}

		// Add file header
		results = append(results, mcp.TextContent{
			Type: "text",
			Text: fmt.Sprintf("--- File: %s ---", path),
		})

		// Check if it's a text file
		if isTextFile(mimeType) {
			// It's a text file, return as text
			results = append(results, mcp.TextContent{
				Type: "text",
				Text: string(content),
			})
		} else if isImageFile(mimeType) {
			// It's an image file, return as image content
			if info.Size() <= MAX_BASE64_SIZE {
				results = append(results, mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Image file: %s (%s, %d bytes)", path, mimeType, info.Size()),
				})
				results = append(results, mcp.ImageContent{
					Type:     "image",
					Data:     base64.StdEncoding.EncodeToString(content),
					MIMEType: mimeType,
				})
			} else {
				// Too large for base64, return a reference
				resourceURI := pathToResourceURI(validPath)
				results = append(results, mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Image file '%s' is too large to display inline (%d bytes). Access it via resource URI: %s",
						path, info.Size(), resourceURI),
				})
			}
		} else {
			// It's another type of binary file
			resourceURI := pathToResourceURI(validPath)

			if info.Size() <= MAX_BASE64_SIZE {
				// Small enough for base64 encoding
				results = append(results, mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Binary file: %s (%s, %d bytes)", path, mimeType, info.Size()),
				})
				results = append(results, mcp.EmbeddedResource{
					Type: "resource",
					Resource: mcp.BlobResourceContents{
						URI:      resourceURI,
						MIMEType: mimeType,
						Blob:     base64.StdEncoding.EncodeToString(content),
					},
				})
			} else {
				// Too large for base64, return a reference
				results = append(results, mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Binary file '%s' (%s, %d bytes). Access it via resource URI: %s",
						path, mimeType, info.Size(), resourceURI),
				})
			}
		}
	}

	return &mcp.CallToolResult{
		Content: results,
	}, nil
}
```

--------------------------------------------------------------------------------
/filesystemserver/handler/tree.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"

	"github.com/mark3labs/mcp-go/mcp"
)

func (fs *FilesystemHandler) HandleTree(
	ctx context.Context,
	request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
	path, err := request.RequireString("path")
	if err != nil {
		return nil, err
	}

	// Handle empty or relative paths like "." or "./" by converting to absolute path
	if path == "." || path == "./" {
		// Get current working directory
		cwd, err := os.Getwd()
		if err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error resolving current directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
		path = cwd
	}

	// Extract depth parameter (optional, default: 3)
	depth := 3 // Default value
	if depthParam, err := request.RequireFloat("depth"); err == nil {
		depth = int(depthParam)
	}

	// Extract follow_symlinks parameter (optional, default: false)
	followSymlinks := false // Default value
	if followParam, err := request.RequireBool("follow_symlinks"); err == nil {
		followSymlinks = followParam
	}

	// Validate the path is within allowed directories
	validPath, err := fs.validatePath(path)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Check if it's a directory
	info, err := os.Stat(validPath)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	if !info.IsDir() {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: "Error: The specified path is not a directory",
				},
			},
			IsError: true,
		}, nil
	}

	// Build the tree structure
	tree, err := fs.buildTree(validPath, depth, 0, followSymlinks)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error building directory tree: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Convert to JSON
	jsonData, err := json.MarshalIndent(tree, "", "  ")
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error generating JSON: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Create resource URI for the directory
	resourceURI := pathToResourceURI(validPath)

	// Return the result
	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: fmt.Sprintf("Directory tree for %s (max depth: %d):\n\n%s", validPath, depth, string(jsonData)),
			},
			mcp.EmbeddedResource{
				Type: "resource",
				Resource: mcp.TextResourceContents{
					URI:      resourceURI,
					MIMEType: "application/json",
					Text:     string(jsonData),
				},
			},
		},
	}, nil
}

// buildTree builds a tree representation of the filesystem starting at the given path
func (fs *FilesystemHandler) buildTree(path string, maxDepth int, currentDepth int, followSymlinks bool) (*FileNode, error) {
	// Validate the path
	validPath, err := fs.validatePath(path)
	if err != nil {
		return nil, err
	}

	// Get file info
	info, err := os.Stat(validPath)
	if err != nil {
		return nil, err
	}

	// Create the node
	node := &FileNode{
		Name:     filepath.Base(validPath),
		Path:     validPath,
		Modified: info.ModTime(),
	}

	// Set type and size
	if info.IsDir() {
		node.Type = "directory"

		// If we haven't reached the max depth, process children
		if currentDepth < maxDepth {
			// Read directory entries
			entries, err := os.ReadDir(validPath)
			if err != nil {
				return nil, err
			}

			// Process each entry
			for _, entry := range entries {
				entryPath := filepath.Join(validPath, entry.Name())

				// Handle symlinks
				if entry.Type()&os.ModeSymlink != 0 {
					if !followSymlinks {
						// Skip symlinks if not following them
						continue
					}

					// Resolve symlink
					linkDest, err := filepath.EvalSymlinks(entryPath)
					if err != nil {
						// Skip invalid symlinks
						continue
					}

					// Validate the symlink destination is within allowed directories
					if !fs.isPathInAllowedDirs(linkDest) {
						// Skip symlinks pointing outside allowed directories
						continue
					}

					entryPath = linkDest
				}

				// Recursively build child node
				childNode, err := fs.buildTree(entryPath, maxDepth, currentDepth+1, followSymlinks)
				if err != nil {
					// Skip entries with errors
					continue
				}

				// Add child to the current node
				node.Children = append(node.Children, childNode)
			}
		}
	} else {
		node.Type = "file"
		node.Size = info.Size()
	}

	return node, nil
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/modify_file.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"fmt"
	"os"
	"regexp"
	"strings"

	"github.com/mark3labs/mcp-go/mcp"
)

// handleModifyFile handles the modify_file tool request
func (fs *FilesystemHandler) HandleModifyFile(
	ctx context.Context,
	request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
	// Extract arguments
	path, err := request.RequireString("path")
	if err != nil {
		return nil, err
	}

	find, err := request.RequireString("find")
	if err != nil {
		return nil, err
	}

	replace, err := request.RequireString("replace")
	if err != nil {
		return nil, err
	}

	// Extract optional arguments with defaults
	allOccurrences := true // Default value
	if val, err := request.RequireBool("all_occurrences"); err == nil {
		allOccurrences = val
	}

	useRegex := false // Default value
	if val, err := request.RequireBool("regex"); err == nil {
		useRegex = val
	}

	// Handle empty or relative paths like "." or "./" by converting to absolute path
	if path == "." || path == "./" {
		// Get current working directory
		cwd, err := os.Getwd()
		if err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error resolving current directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
		path = cwd
	}

	// Validate path is within allowed directories
	validPath, err := fs.validatePath(path)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Check if it's a directory
	if info, err := os.Stat(validPath); err == nil && info.IsDir() {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: "Error: Cannot modify a directory",
				},
			},
			IsError: true,
		}, nil
	}

	// Check if file exists
	if _, err := os.Stat(validPath); os.IsNotExist(err) {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: File not found: %s", path),
				},
			},
			IsError: true,
		}, nil
	}

	// Read file content
	content, err := os.ReadFile(validPath)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error reading file: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	originalContent := string(content)
	modifiedContent := ""
	replacementCount := 0

	// Perform the replacement
	if useRegex {
		re, err := regexp.Compile(find)
		if err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error: Invalid regular expression: %v", err),
					},
				},
				IsError: true,
			}, nil
		}

		if allOccurrences {
			modifiedContent = re.ReplaceAllString(originalContent, replace)
			replacementCount = len(re.FindAllString(originalContent, -1))
		} else {
			matched := re.FindStringIndex(originalContent)
			if matched != nil {
				replacementCount = 1
				modifiedContent = originalContent[:matched[0]] + replace + originalContent[matched[1]:]
			} else {
				modifiedContent = originalContent
				replacementCount = 0
			}
		}
	} else {
		if allOccurrences {
			replacementCount = strings.Count(originalContent, find)
			modifiedContent = strings.ReplaceAll(originalContent, find, replace)
		} else {
			if index := strings.Index(originalContent, find); index != -1 {
				replacementCount = 1
				modifiedContent = originalContent[:index] + replace + originalContent[index+len(find):]
			} else {
				modifiedContent = originalContent
				replacementCount = 0
			}
		}
	}

	// Write modified content back to file
	if err := os.WriteFile(validPath, []byte(modifiedContent), 0644); err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error writing to file: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Create response
	resourceURI := pathToResourceURI(validPath)

	// Get file info for the response
	info, err := os.Stat(validPath)
	if err != nil {
		// File was written but we couldn't get info
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("File modified successfully. Made %d replacement(s).", replacementCount),
				},
			},
		}, nil
	}

	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: fmt.Sprintf("File modified successfully. Made %d replacement(s) in %s (file size: %d bytes)",
					replacementCount, path, info.Size()),
			},
			mcp.EmbeddedResource{
				Type: "resource",
				Resource: mcp.TextResourceContents{
					URI:      resourceURI,
					MIMEType: "text/plain",
					Text:     fmt.Sprintf("Modified file: %s (%d bytes)", validPath, info.Size()),
				},
			},
		},
	}, nil
}
```

--------------------------------------------------------------------------------
/filesystemserver/handler/copy_file.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"github.com/mark3labs/mcp-go/mcp"
)

func (fs *FilesystemHandler) HandleCopyFile(
	ctx context.Context,
	request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
	source, err := request.RequireString("source")
	if err != nil {
		return nil, err
	}
	destination, err := request.RequireString("destination")
	if err != nil {
		return nil, err
	}

	// Handle empty or relative paths for source
	if source == "." || source == "./" {
		cwd, err := os.Getwd()
		if err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error resolving current directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
		source = cwd
	}
	if destination == "." || destination == "./" {
		cwd, err := os.Getwd()
		if err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error resolving current directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
		destination = cwd
	}

	validSource, err := fs.validatePath(source)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error with source path: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Check if source exists
	srcInfo, err := os.Stat(validSource)
	if os.IsNotExist(err) {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: Source does not exist: %s", source),
				},
			},
			IsError: true,
		}, nil
	} else if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error accessing source: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	validDest, err := fs.validatePath(destination)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error with destination path: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Create parent directory for destination if it doesn't exist
	destDir := filepath.Dir(validDest)
	if err := os.MkdirAll(destDir, 0755); err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error creating destination directory: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Perform the copy operation based on whether source is a file or directory
	if srcInfo.IsDir() {
		// It's a directory, copy recursively
		if err := copyDir(validSource, validDest); err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error copying directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
	} else {
		// It's a file, copy directly
		if err := copyFile(validSource, validDest); err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error copying file: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
	}

	resourceURI := pathToResourceURI(validDest)
	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: fmt.Sprintf(
					"Successfully copied %s to %s",
					source,
					destination,
				),
			},
			mcp.EmbeddedResource{
				Type: "resource",
				Resource: mcp.TextResourceContents{
					URI:      resourceURI,
					MIMEType: "text/plain",
					Text:     fmt.Sprintf("Copied file: %s", validDest),
				},
			},
		},
	}, nil
}

// copyFile copies a single file from src to dst
func copyFile(src, dst string) error {
	// Open the source file
	sourceFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer sourceFile.Close()

	// Create the destination file
	destFile, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer destFile.Close()

	// Copy the contents
	if _, err := io.Copy(destFile, sourceFile); err != nil {
		return err
	}

	// Get source file mode
	sourceInfo, err := os.Stat(src)
	if err != nil {
		return err
	}

	// Set the same file mode on destination
	return os.Chmod(dst, sourceInfo.Mode())
}

// copyDir recursively copies a directory tree from src to dst
func copyDir(src, dst string) error {
	// Get properties of source dir
	srcInfo, err := os.Stat(src)
	if err != nil {
		return err
	}

	// Create the destination directory with the same permissions
	if err = os.MkdirAll(dst, srcInfo.Mode()); err != nil {
		return err
	}

	// Read directory entries
	entries, err := os.ReadDir(src)
	if err != nil {
		return err
	}

	for _, entry := range entries {
		srcPath := filepath.Join(src, entry.Name())
		dstPath := filepath.Join(dst, entry.Name())

		// Handle symlinks
		if entry.Type()&os.ModeSymlink != 0 {
			// For simplicity, we'll skip symlinks in this implementation
			continue
		}

		// Recursively copy subdirectories or copy files
		if entry.IsDir() {
			if err = copyDir(srcPath, dstPath); err != nil {
				return err
			}
		} else {
			if err = copyFile(srcPath, dstPath); err != nil {
				return err
			}
		}
	}

	return nil
}
```

--------------------------------------------------------------------------------
/filesystemserver/handler/read_file.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"encoding/base64"
	"fmt"
	"os"

	"github.com/mark3labs/mcp-go/mcp"
)

func (fs *FilesystemHandler) HandleReadFile(
	ctx context.Context,
	request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
	path, err := request.RequireString("path")
	if err != nil {
		return nil, err
	}

	// Handle empty or relative paths like "." or "./" by converting to absolute path
	if path == "." || path == "./" {
		// Get current working directory
		cwd, err := os.Getwd()
		if err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error resolving current directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
		path = cwd
	}

	validPath, err := fs.validatePath(path)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Check if it's a directory
	info, err := os.Stat(validPath)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	if info.IsDir() {
		// For directories, return a resource reference instead
		resourceURI := pathToResourceURI(validPath)
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("This is a directory. Use the resource URI to browse its contents: %s", resourceURI),
				},
				mcp.EmbeddedResource{
					Type: "resource",
					Resource: mcp.TextResourceContents{
						URI:      resourceURI,
						MIMEType: "text/plain",
						Text:     fmt.Sprintf("Directory: %s", validPath),
					},
				},
			},
		}, nil
	}

	// Determine MIME type
	mimeType := detectMimeType(validPath)

	// Check file size
	if info.Size() > MAX_INLINE_SIZE {
		// File is too large to inline, return a resource reference
		resourceURI := pathToResourceURI(validPath)
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("File is too large to display inline (%d bytes). Access it via resource URI: %s", info.Size(), resourceURI),
				},
				mcp.EmbeddedResource{
					Type: "resource",
					Resource: mcp.TextResourceContents{
						URI:      resourceURI,
						MIMEType: "text/plain",
						Text:     fmt.Sprintf("Large file: %s (%s, %d bytes)", validPath, mimeType, info.Size()),
					},
				},
			},
		}, nil
	}

	// Read file content
	content, err := os.ReadFile(validPath)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error reading file: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Check if it's a text file
	if isTextFile(mimeType) {
		// It's a text file, return as text
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: string(content),
				},
			},
		}, nil
	} else if isImageFile(mimeType) {
		// It's an image file, return as image content
		if info.Size() <= MAX_BASE64_SIZE {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Image file: %s (%s, %d bytes)", validPath, mimeType, info.Size()),
					},
					mcp.ImageContent{
						Type:     "image",
						Data:     base64.StdEncoding.EncodeToString(content),
						MIMEType: mimeType,
					},
				},
			}, nil
		} else {
			// Too large for base64, return a reference
			resourceURI := pathToResourceURI(validPath)
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Image file is too large to display inline (%d bytes). Access it via resource URI: %s", info.Size(), resourceURI),
					},
					mcp.EmbeddedResource{
						Type: "resource",
						Resource: mcp.TextResourceContents{
							URI:      resourceURI,
							MIMEType: "text/plain",
							Text:     fmt.Sprintf("Large image: %s (%s, %d bytes)", validPath, mimeType, info.Size()),
						},
					},
				},
			}, nil
		}
	} else {
		// It's another type of binary file
		resourceURI := pathToResourceURI(validPath)

		if info.Size() <= MAX_BASE64_SIZE {
			// Small enough for base64 encoding
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Binary file: %s (%s, %d bytes)", validPath, mimeType, info.Size()),
					},
					mcp.EmbeddedResource{
						Type: "resource",
						Resource: mcp.BlobResourceContents{
							URI:      resourceURI,
							MIMEType: mimeType,
							Blob:     base64.StdEncoding.EncodeToString(content),
						},
					},
				},
			}, nil
		} else {
			// Too large for base64, return a reference
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Binary file: %s (%s, %d bytes). Access it via resource URI: %s", validPath, mimeType, info.Size(), resourceURI),
					},
					mcp.EmbeddedResource{
						Type: "resource",
						Resource: mcp.TextResourceContents{
							URI:      resourceURI,
							MIMEType: "text/plain",
							Text:     fmt.Sprintf("Binary file: %s (%s, %d bytes)", validPath, mimeType, info.Size()),
						},
					},
				},
			}, nil
		}
	}
}
```

--------------------------------------------------------------------------------
/filesystemserver/server.go:
--------------------------------------------------------------------------------

```go
package filesystemserver

import (
	"github.com/mark3labs/mcp-filesystem-server/filesystemserver/handler"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
)

var Version = "dev"

func NewFilesystemServer(allowedDirs []string) (*server.MCPServer, error) {

	h, err := handler.NewFilesystemHandler(allowedDirs)
	if err != nil {
		return nil, err
	}

	s := server.NewMCPServer(
		"secure-filesystem-server",
		Version,
		server.WithResourceCapabilities(true, true),
	)

	// Register resource handlers
	s.AddResource(mcp.NewResource(
		"file://",
		"File System",
		mcp.WithResourceDescription("Access to files and directories on the local file system"),
	), h.HandleReadResource)

	// Register tool handlers
	s.AddTool(mcp.NewTool(
		"read_file",
		mcp.WithDescription("Read the complete contents of a file from the file system."),
		mcp.WithString("path",
			mcp.Description("Path to the file to read"),
			mcp.Required(),
		),
	), h.HandleReadFile)

	s.AddTool(mcp.NewTool(
		"write_file",
		mcp.WithDescription("Create a new file or overwrite an existing file with new content."),
		mcp.WithString("path",
			mcp.Description("Path where to write the file"),
			mcp.Required(),
		),
		mcp.WithString("content",
			mcp.Description("Content to write to the file"),
			mcp.Required(),
		),
	), h.HandleWriteFile)

	s.AddTool(mcp.NewTool(
		"list_directory",
		mcp.WithDescription("Get a detailed listing of all files and directories in a specified path."),
		mcp.WithString("path",
			mcp.Description("Path of the directory to list"),
			mcp.Required(),
		),
	), h.HandleListDirectory)

	s.AddTool(mcp.NewTool(
		"create_directory",
		mcp.WithDescription("Create a new directory or ensure a directory exists."),
		mcp.WithString("path",
			mcp.Description("Path of the directory to create"),
			mcp.Required(),
		),
	), h.HandleCreateDirectory)

	s.AddTool(mcp.NewTool(
		"copy_file",
		mcp.WithDescription("Copy files and directories."),
		mcp.WithString("source",
			mcp.Description("Source path of the file or directory"),
			mcp.Required(),
		),
		mcp.WithString("destination",
			mcp.Description("Destination path"),
			mcp.Required(),
		),
	), h.HandleCopyFile)

	s.AddTool(mcp.NewTool(
		"move_file",
		mcp.WithDescription("Move or rename files and directories."),
		mcp.WithString("source",
			mcp.Description("Source path of the file or directory"),
			mcp.Required(),
		),
		mcp.WithString("destination",
			mcp.Description("Destination path"),
			mcp.Required(),
		),
	), h.HandleMoveFile)

	s.AddTool(mcp.NewTool(
		"search_files",
		mcp.WithDescription("Recursively search for files and directories matching a pattern."),
		mcp.WithString("path",
			mcp.Description("Starting path for the search"),
			mcp.Required(),
		),
		mcp.WithString("pattern",
			mcp.Description("Search pattern to match against file names"),
			mcp.Required(),
		),
	), h.HandleSearchFiles)

	s.AddTool(mcp.NewTool(
		"get_file_info",
		mcp.WithDescription("Retrieve detailed metadata about a file or directory."),
		mcp.WithString("path",
			mcp.Description("Path to the file or directory"),
			mcp.Required(),
		),
	), h.HandleGetFileInfo)

	s.AddTool(mcp.NewTool(
		"list_allowed_directories",
		mcp.WithDescription("Returns the list of directories that this server is allowed to access."),
	), h.HandleListAllowedDirectories)

	s.AddTool(mcp.NewTool(
		"read_multiple_files",
		mcp.WithDescription("Read the contents of multiple files in a single operation."),
		mcp.WithArray("paths",
			mcp.Description("List of file paths to read"),
			mcp.Required(),
			mcp.Items(map[string]any{"type": "string"}),
		),
	), h.HandleReadMultipleFiles)

	s.AddTool(mcp.NewTool(
		"tree",
		mcp.WithDescription("Returns a hierarchical JSON representation of a directory structure."),
		mcp.WithString("path",
			mcp.Description("Path of the directory to traverse"),
			mcp.Required(),
		),
		mcp.WithNumber("depth",
			mcp.Description("Maximum depth to traverse (default: 3)"),
		),
		mcp.WithBoolean("follow_symlinks",
			mcp.Description("Whether to follow symbolic links (default: false)"),
		),
	), h.HandleTree)

	s.AddTool(mcp.NewTool(
		"delete_file",
		mcp.WithDescription("Delete a file or directory from the file system."),
		mcp.WithString("path",
			mcp.Description("Path to the file or directory to delete"),
			mcp.Required(),
		),
		mcp.WithBoolean("recursive",
			mcp.Description("Whether to recursively delete directories (default: false)"),
		),
	), h.HandleDeleteFile)

	s.AddTool(mcp.NewTool(
		"modify_file",
		mcp.WithDescription("Update file by finding and replacing text. Provides a simple pattern matching interface without needing exact character positions."),
		mcp.WithString("path",
			mcp.Description("Path to the file to modify"),
			mcp.Required(),
		),
		mcp.WithString("find",
			mcp.Description("Text to search for (exact match or regex pattern)"),
			mcp.Required(),
		),
		mcp.WithString("replace",
			mcp.Description("Text to replace with"),
			mcp.Required(),
		),
		mcp.WithBoolean("all_occurrences",
			mcp.Description("Replace all occurrences of the matching text (default: true)"),
		),
		mcp.WithBoolean("regex",
			mcp.Description("Treat the find pattern as a regular expression (default: false)"),
		),
	), h.HandleModifyFile)

	s.AddTool(mcp.NewTool(
		"search_within_files",
		mcp.WithDescription("Search for text within file contents. Unlike search_files which only searches file names, this tool scans the actual contents of text files for matching substrings. Binary files are automatically excluded from the search. Reports file paths and line numbers where matches are found."),
		mcp.WithString("path",
			mcp.Description("Starting path for the search (must be a directory)"),
			mcp.Required(),
		),
		mcp.WithString("substring",
			mcp.Description("Text to search for within file contents"),
			mcp.Required(),
		),
		mcp.WithNumber("depth",
			mcp.Description("Maximum directory depth to search (default: unlimited)"),
		),
		mcp.WithNumber("max_results",
			mcp.Description("Maximum number of results to return (default: 1000)"),
		),
	), h.HandleSearchWithinFiles)

	return s, nil
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/tree_test.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestHandleTree(t *testing.T) {
	// Setup a temporary directory for the test
	tmpDir := t.TempDir()

	// Create a handler with the temp dir as an allowed path
	allowedDirs := resolveAllowedDirs(t, tmpDir)
	fsHandler, err := NewFilesystemHandler(allowedDirs)
	require.NoError(t, err)

	ctx := context.Background()

	// Create test directory structure
	// /tmpDir/
	//   ├── file1.txt
	//   ├── subdir1/
	//   │   ├── file2.txt
	//   │   └── subdir2/
	//   │       └── file3.txt
	//   └── emptydir/

	file1Path := filepath.Join(tmpDir, "file1.txt")
	err = os.WriteFile(file1Path, []byte("content1"), 0644)
	require.NoError(t, err)

	subdir1Path := filepath.Join(tmpDir, "subdir1")
	err = os.Mkdir(subdir1Path, 0755)
	require.NoError(t, err)

	file2Path := filepath.Join(subdir1Path, "file2.txt")
	err = os.WriteFile(file2Path, []byte("content2"), 0644)
	require.NoError(t, err)

	subdir2Path := filepath.Join(subdir1Path, "subdir2")
	err = os.Mkdir(subdir2Path, 0755)
	require.NoError(t, err)

	file3Path := filepath.Join(subdir2Path, "file3.txt")
	err = os.WriteFile(file3Path, []byte("content3"), 0644)
	require.NoError(t, err)

	emptydirPath := filepath.Join(tmpDir, "emptydir")
	err = os.Mkdir(emptydirPath, 0755)
	require.NoError(t, err)

	t.Run("tree with default depth", func(t *testing.T) {
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": tmpDir,
				},
			},
		}

		res, err := fsHandler.HandleTree(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Verify the response contains tree structure
		require.Len(t, res.Content, 2)
		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "Directory tree for")
		assert.Contains(t, textContent.Text, "max depth: 3")

		// Parse the JSON to verify structure
		lines := textContent.Text
		assert.Contains(t, lines, "file1.txt")
		assert.Contains(t, lines, "subdir1")
		assert.Contains(t, lines, "file2.txt")
		assert.Contains(t, lines, "subdir2")
		assert.Contains(t, lines, "file3.txt")
		assert.Contains(t, lines, "emptydir")

		// Verify embedded resource
		embeddedResource := res.Content[1].(mcp.EmbeddedResource)
		assert.Equal(t, "resource", embeddedResource.Type)
		assert.Equal(t, "application/json", embeddedResource.Resource.(mcp.TextResourceContents).MIMEType)
	})

	t.Run("tree with custom depth", func(t *testing.T) {
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path":  tmpDir,
					"depth": 2.0, // Only go 2 levels deep
				},
			},
		}

		res, err := fsHandler.HandleTree(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "max depth: 2")

		// Should include file1.txt, subdir1, file2.txt, subdir2, emptydir
		// but NOT file3.txt (which is at depth 3)
		assert.Contains(t, textContent.Text, "file1.txt")
		assert.Contains(t, textContent.Text, "subdir1")
		assert.Contains(t, textContent.Text, "file2.txt")
		assert.Contains(t, textContent.Text, "subdir2")
		assert.Contains(t, textContent.Text, "emptydir")
		// file3.txt should not be included at depth 2
		assert.NotContains(t, textContent.Text, "file3.txt")
	})

	t.Run("tree with depth 1", func(t *testing.T) {
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path":  tmpDir,
					"depth": 1.0, // Only show immediate children
				},
			},
		}

		res, err := fsHandler.HandleTree(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "max depth: 1")

		// Should only include immediate children
		assert.Contains(t, textContent.Text, "file1.txt")
		assert.Contains(t, textContent.Text, "subdir1")
		assert.Contains(t, textContent.Text, "emptydir")
		// Should not include nested files
		assert.NotContains(t, textContent.Text, "file2.txt")
		assert.NotContains(t, textContent.Text, "subdir2")
		assert.NotContains(t, textContent.Text, "file3.txt")
	})

	t.Run("tree of empty directory", func(t *testing.T) {
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": emptydirPath,
				},
			},
		}

		res, err := fsHandler.HandleTree(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "Directory tree for")

		// Parse JSON to verify it's a directory with no children
		jsonStart := textContent.Text[strings.Index(textContent.Text, "{"):]
		var tree FileNode
		err = json.Unmarshal([]byte(jsonStart), &tree)
		require.NoError(t, err)
		assert.Equal(t, "directory", tree.Type)
		assert.Equal(t, "emptydir", tree.Name)
		assert.Nil(t, tree.Children)
	})

	t.Run("try to tree a file instead of directory", func(t *testing.T) {
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": file1Path,
				},
			},
		}

		res, err := fsHandler.HandleTree(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)

		require.Len(t, res.Content, 1)
		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "not a directory")
	})

	t.Run("try to tree non-existent directory", func(t *testing.T) {
		nonExistentPath := filepath.Join(tmpDir, "non_existent_directory")

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": nonExistentPath,
				},
			},
		}

		res, err := fsHandler.HandleTree(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)
	})

	t.Run("path is in a non-allowed directory", func(t *testing.T) {
		otherDir := t.TempDir()

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"path": otherDir,
				},
			},
		}

		res, err := fsHandler.HandleTree(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)
	})
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/read_multiple_files_test.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"context"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestHandleReadMultipleFiles(t *testing.T) {
	// Setup a temporary directory for the test
	tmpDir := t.TempDir()

	// Create a handler with the temp dir as an allowed path
	allowedDirs := resolveAllowedDirs(t, tmpDir)
	fsHandler, err := NewFilesystemHandler(allowedDirs)
	require.NoError(t, err)

	ctx := context.Background()

	// Create test files
	file1Path := filepath.Join(tmpDir, "file1.txt")
	file1Content := "This is the content of file 1"
	err = os.WriteFile(file1Path, []byte(file1Content), 0644)
	require.NoError(t, err)

	file2Path := filepath.Join(tmpDir, "file2.txt")
	file2Content := "This is the content of file 2"
	err = os.WriteFile(file2Path, []byte(file2Content), 0644)
	require.NoError(t, err)

	// Create a directory
	dirPath := filepath.Join(tmpDir, "test_directory")
	err = os.Mkdir(dirPath, 0755)
	require.NoError(t, err)

	t.Run("read multiple text files", func(t *testing.T) {
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"paths": []string{file1Path, file2Path},
				},
			},
		}

		res, err := fsHandler.HandleReadMultipleFiles(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Verify the response contains content from both files
		require.GreaterOrEqual(t, len(res.Content), 4) // At least 2 headers + 2 content blocks

		// Convert all content to strings for easier checking
		var contentTexts []string
		for _, content := range res.Content {
			if textContent, ok := content.(mcp.TextContent); ok {
				contentTexts = append(contentTexts, textContent.Text)
			}
		}

		allText := strings.Join(contentTexts, "\n")
		assert.Contains(t, allText, "--- File: "+file1Path+" ---")
		assert.Contains(t, allText, "--- File: "+file2Path+" ---")
		assert.Contains(t, allText, file1Content)
		assert.Contains(t, allText, file2Content)
	})

	t.Run("read single file", func(t *testing.T) {
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"paths": []string{file1Path},
				},
			},
		}

		res, err := fsHandler.HandleReadMultipleFiles(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Verify the response contains content from the file
		require.GreaterOrEqual(t, len(res.Content), 2) // At least 1 header + 1 content block

		var contentTexts []string
		for _, content := range res.Content {
			if textContent, ok := content.(mcp.TextContent); ok {
				contentTexts = append(contentTexts, textContent.Text)
			}
		}

		allText := strings.Join(contentTexts, "\n")
		assert.Contains(t, allText, "--- File: "+file1Path+" ---")
		assert.Contains(t, allText, file1Content)
	})

	t.Run("try to read a directory", func(t *testing.T) {
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"paths": []string{dirPath},
				},
			},
		}

		res, err := fsHandler.HandleReadMultipleFiles(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Should get a message about it being a directory
		require.Len(t, res.Content, 1)
		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "is a directory")
		assert.Contains(t, textContent.Text, "Use list_directory tool")
	})

	t.Run("try to read non-existent file", func(t *testing.T) {
		nonExistentPath := filepath.Join(tmpDir, "non_existent.txt")

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"paths": []string{nonExistentPath},
				},
			},
		}

		res, err := fsHandler.HandleReadMultipleFiles(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError) // The operation succeeds but individual files may have errors

		// Should get an error message about the file not existing
		require.Len(t, res.Content, 1)
		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "Error accessing")
		assert.Contains(t, textContent.Text, nonExistentPath)
	})

	t.Run("mix of valid and invalid files", func(t *testing.T) {
		nonExistentPath := filepath.Join(tmpDir, "non_existent.txt")

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"paths": []string{file1Path, nonExistentPath, file2Path},
				},
			},
		}

		res, err := fsHandler.HandleReadMultipleFiles(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError)

		// Should have content for valid files and error messages for invalid ones
		require.GreaterOrEqual(t, len(res.Content), 5) // At least 2 headers + 2 content blocks + 1 error

		var contentTexts []string
		for _, content := range res.Content {
			if textContent, ok := content.(mcp.TextContent); ok {
				contentTexts = append(contentTexts, textContent.Text)
			}
		}

		allText := strings.Join(contentTexts, "\n")
		assert.Contains(t, allText, "--- File: "+file1Path+" ---")
		assert.Contains(t, allText, "--- File: "+file2Path+" ---")
		assert.Contains(t, allText, file1Content)
		assert.Contains(t, allText, file2Content)
		assert.Contains(t, allText, "Error accessing")
		assert.Contains(t, allText, nonExistentPath)
	})

	t.Run("no files specified", func(t *testing.T) {
		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"paths": []string{},
				},
			},
		}

		res, err := fsHandler.HandleReadMultipleFiles(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)

		require.Len(t, res.Content, 1)
		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "No files specified to read")
	})

	t.Run("too many files", func(t *testing.T) {
		// Create a slice with more than 50 files (the maximum)
		var manyPaths []string
		for i := 0; i < 51; i++ {
			manyPaths = append(manyPaths, filepath.Join(tmpDir, "file.txt"))
		}

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"paths": manyPaths,
				},
			},
		}

		res, err := fsHandler.HandleReadMultipleFiles(ctx, req)
		require.NoError(t, err)
		require.True(t, res.IsError)

		require.Len(t, res.Content, 1)
		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "Too many files requested")
		assert.Contains(t, textContent.Text, "Maximum is 50")
	})

	t.Run("path in non-allowed directory", func(t *testing.T) {
		otherDir := t.TempDir()
		otherFile := filepath.Join(otherDir, "other.txt")

		req := mcp.CallToolRequest{
			Params: mcp.CallToolParams{
				Arguments: map[string]interface{}{
					"paths": []string{otherFile},
				},
			},
		}

		res, err := fsHandler.HandleReadMultipleFiles(ctx, req)
		require.NoError(t, err)
		require.False(t, res.IsError) // The operation succeeds but individual files may have errors

		require.Len(t, res.Content, 1)
		textContent := res.Content[0].(mcp.TextContent)
		assert.Contains(t, textContent.Text, "Error with path")
		assert.Contains(t, textContent.Text, otherFile)
	})
}

```

--------------------------------------------------------------------------------
/filesystemserver/handler/search_within_files.go:
--------------------------------------------------------------------------------

```go
package handler

import (
	"bufio"
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/mark3labs/mcp-go/mcp"
)

func (fs *FilesystemHandler) HandleSearchWithinFiles(
	ctx context.Context,
	request mcp.CallToolRequest,
) (*mcp.CallToolResult, error) {
	// Extract and validate parameters
	path, err := request.RequireString("path")
	if err != nil {
		return nil, err
	}
	substring, err := request.RequireString("substring")
	if err != nil {
		return nil, err
	}
	if substring == "" {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: "Error: substring cannot be empty",
				},
			},
			IsError: true,
		}, nil
	}

	// Extract optional depth parameter
	maxDepth := 0 // 0 means unlimited
	if depthArg, err := request.RequireFloat("depth"); err == nil {
		maxDepth = int(depthArg)
		if maxDepth < 0 {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: "Error: depth cannot be negative",
					},
				},
				IsError: true,
			}, nil
		}
	}

	// Extract optional max_results parameter
	maxResults := MAX_SEARCH_RESULTS // default limit
	if maxResultsArg, err := request.RequireFloat("max_results"); err == nil {
		maxResults = int(maxResultsArg)
		if maxResults <= 0 {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: "Error: max_results must be positive",
					},
				},
				IsError: true,
			}, nil
		}
	}

	// Handle empty or relative paths like "." or "./" by converting to absolute path
	if path == "." || path == "./" {
		// Get current working directory
		cwd, err := os.Getwd()
		if err != nil {
			return &mcp.CallToolResult{
				Content: []mcp.Content{
					mcp.TextContent{
						Type: "text",
						Text: fmt.Sprintf("Error resolving current directory: %v", err),
					},
				},
				IsError: true,
			}, nil
		}
		path = cwd
	}

	validPath, err := fs.validatePath(path)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	// Check if the path is a directory
	info, err := os.Stat(validPath)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	if !info.IsDir() {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: "Error: search path must be a directory",
				},
			},
			IsError: true,
		}, nil
	}

	// Perform the search
	results, err := searchWithinFiles(validPath, substring, maxDepth, maxResults, fs)
	if err != nil {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("Error searching within files: %v", err),
				},
			},
			IsError: true,
		}, nil
	}

	if len(results) == 0 {
		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.TextContent{
					Type: "text",
					Text: fmt.Sprintf("No occurrences of '%s' found in files under %s", substring, path),
				},
			},
		}, nil
	}

	// Format search results
	var formattedResults strings.Builder
	formattedResults.WriteString(fmt.Sprintf("Found %d occurrences of '%s':\n\n", len(results), substring))

	// Group results by file for easier readability
	fileResultsMap := make(map[string][]SearchResult)
	for _, result := range results {
		fileResultsMap[result.FilePath] = append(fileResultsMap[result.FilePath], result)
	}

	// Display results grouped by file
	for filePath, fileResults := range fileResultsMap {
		resourceURI := pathToResourceURI(filePath)
		formattedResults.WriteString(fmt.Sprintf("File: %s (%s)\n", filePath, resourceURI))

		for _, result := range fileResults {
			// Truncate line content if too long (keeping context around the match)
			lineContent := result.LineContent
			if len(lineContent) > 100 {
				// Find the substring position
				substrPos := strings.Index(strings.ToLower(lineContent), strings.ToLower(substring))

				// Calculate start and end positions for context
				contextStart := max(0, substrPos-30)
				contextEnd := min(len(lineContent), substrPos+len(substring)+30)

				if contextStart > 0 {
					lineContent = "..." + lineContent[contextStart:contextEnd]
				} else {
					lineContent = lineContent[:contextEnd]
				}

				if contextEnd < len(result.LineContent) {
					lineContent += "..."
				}
			}

			formattedResults.WriteString(fmt.Sprintf("  Line %d: %s\n", result.LineNumber, lineContent))
		}
		formattedResults.WriteString("\n")
	}

	// If results were limited, note this in the output
	if len(results) >= maxResults {
		formattedResults.WriteString(fmt.Sprintf("\nNote: Results limited to %d matches. There may be more occurrences.", maxResults))
	}

	return &mcp.CallToolResult{
		Content: []mcp.Content{
			mcp.TextContent{
				Type: "text",
				Text: formattedResults.String(),
			},
		},
	}, nil
}

// searchWithinFiles searches for a substring within file contents
func searchWithinFiles(
	rootPath, substring string, maxDepth int, maxResults int, fs *FilesystemHandler,
) ([]SearchResult, error) {
	var results []SearchResult
	resultCount := 0
	currentDepth := 0

	// Walk the directory tree
	err := filepath.Walk(
		rootPath,
		func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return nil // Skip errors and continue
			}

			// Check if we've reached the maximum number of results
			if resultCount >= maxResults {
				return filepath.SkipDir
			}

			// Try to validate path
			validPath, err := fs.validatePath(path)
			if err != nil {
				return nil // Skip invalid paths
			}

			// Skip directories, only search files
			if info.IsDir() {
				// Calculate depth for this directory
				relPath, err := filepath.Rel(rootPath, path)
				if err != nil {
					return nil // Skip on error
				}

				// Count separators to determine depth (empty or "." means we're at rootPath)
				if relPath == "" || relPath == "." {
					currentDepth = 0
				} else {
					currentDepth = strings.Count(relPath, string(filepath.Separator)) + 1
				}

				// Skip directories beyond max depth if specified
				if maxDepth > 0 && currentDepth >= maxDepth {
					return filepath.SkipDir
				}
				return nil
			}

			// Skip files that are too large
			if info.Size() > MAX_SEARCHABLE_SIZE {
				return nil
			}

			// Determine MIME type and skip non-text files
			mimeType := detectMimeType(validPath)
			if !isTextFile(mimeType) {
				return nil
			}

			// Open the file and search for the substring
			file, err := os.Open(validPath)
			if err != nil {
				return nil // Skip files that can't be opened
			}
			defer file.Close()

			// Create a scanner to read the file line by line
			scanner := bufio.NewScanner(file)
			lineNum := 0

			// Scan each line
			for scanner.Scan() {
				lineNum++
				line := scanner.Text()

				// Check if the line contains the substring
				if strings.Contains(line, substring) {
					// Add to results
					results = append(results, SearchResult{
						FilePath:    validPath,
						LineNumber:  lineNum,
						LineContent: line,
						ResourceURI: pathToResourceURI(validPath),
					})
					resultCount++

					// Check if we've reached the maximum results
					if resultCount >= maxResults {
						return filepath.SkipDir
					}
				}
			}

			// Check for scanner errors
			if err := scanner.Err(); err != nil {
				return nil // Skip files with scanning errors
			}

			return nil
		},
	)

	if err != nil {
		return nil, err
	}

	return results, nil
}

// Helper function since Go < 1.21 doesn't have min/max functions
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

// Helper function since Go < 1.21 doesn't have min/max functions
func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

```