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

```
├── .env.example
├── .eslintrc.json
├── .gitignore
├── .npmignore
├── docker
│   ├── docker-compose.yml
│   ├── Dockerfile
│   └── webdav_config.yml
├── LICENSE
├── NPM_PUBLISH.md
├── package.json
├── PASSWORD_ENCRYPTION.md
├── README.md
├── setup.bat
├── setup.sh
├── src
│   ├── bin.ts
│   ├── config
│   │   └── validation.ts
│   ├── handlers
│   │   ├── prompt-handlers.ts
│   │   ├── resource-handlers.ts
│   │   └── tool-handlers.ts
│   ├── index.ts
│   ├── lib.ts
│   ├── middleware
│   │   └── auth-middleware.ts
│   ├── servers
│   │   └── express-server.ts
│   ├── services
│   │   ├── webdav-connection-pool.ts
│   │   └── webdav-service.ts
│   └── utils
│       ├── generate-hash.ts
│       ├── logger.ts
│       └── password-utils.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------

```json
{
  "root": true,
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": 2021,
    "sourceType": "module"
  },
  "plugins": [
    "@typescript-eslint"
  ],
  "rules": {
    "no-console": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off"
  }
}

```

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

```
# Dependencies
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
package-lock.json

# Build output
dist/
build/
out/
*.tsbuildinfo

# Environment
.env
.env.local
.env.development
.env.test
.env.production

# IDE and editors
.idea/
.vscode/
*.code-workspace
.DS_Store
*.swp
*.swo

# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Testing
coverage/
.nyc_output/

# Miscellaneous
.tmp/
.temp/

```

--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------

```
# Source files
src/
test/
tests/

# Development files
.github/
.vscode/
.eslintrc.json
jest.config.js
tsconfig.json
setup.sh
setup.bat
verify-build.bat
build-test.bat
npm-pack.bat

# Documentation
CLAUDE_INTEGRATION.md
SUMMARY.md
SUMMARY_OF_FIXES.md
NPM_PUBLISH.md

# Docker files
Dockerfile
docker-compose.yml

# Logs
*.log
errors.log
errors.1.log

# Test directories
test-build/
coverage/
__tests__/

# Environment files
.env
.env.example

# Git files
.git/
.gitignore

```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
# WebDAV configuration
WEBDAV_ROOT_URL=http://localhost:4080
WEBDAV_ROOT_PATH=/webdav

# WebDAV authentication (optional)
WEBDAV_AUTH_ENABLED=true
WEBDAV_USERNAME=admin

# WebDAV password must be plain text (required when auth enabled)
WEBDAV_PASSWORD=password

# Server configuration (for HTTP mode)
SERVER_PORT=3000

# Authentication configuration (for HTTP mode)
AUTH_ENABLED=true
AUTH_USERNAME=user
AUTH_PASSWORD=pass
AUTH_REALM=MCP WebDAV Server

# Auth password for MCP server can be a bcrypt hash (unlike WebDAV passwords)
# AUTH_PASSWORD={bcrypt}$2y$05$CyLKnUwn9fqqKQFEbxpZFuE9mzWR/x8t6TE7.CgAN0oT8I/5jKJBy

```

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

```markdown
# WebDAV MCP Server

A Model Context Protocol (MCP) server that enables CRUD operations on a WebDAV endpoint with basic authentication. This server enables Claude Desktop and other MCP clients to interact with WebDAV file systems through natural language commands.

## Features

- Connect to any WebDAV server with optional authentication
- Perform CRUD operations on files and directories
- Expose file operations as MCP resources and tools
- Run via stdio transport (for Claude Desktop integration) or HTTP/SSE transport
- Secure access with optional basic authentication
- Support for bcrypt-encrypted passwords for MCP server authentication (WebDAV passwords must be plain text due to protocol limitations)
- Connection pooling for better performance with WebDAV servers
- Configuration validation using Zod
- Structured logging for better troubleshooting

## Prerequisites

- Node.js 18 or later
- npm or yarn
- WebDAV server (for actual file operations)

## Installation

### Option 1: Install from npm package

```bash
# Global installation
npm install -g webdav-mcp-server

# Or with npx
npx webdav-mcp-server
```

### Option 2: Clone and build from source

```bash
# Clone repository
git clone https://github.com/yourusername/webdav-mcp-server.git
cd webdav-mcp-server

# Install dependencies
npm install

# Build the application
npm run build
```

### Option 3: Docker

```bash
# Build the Docker image
docker build -t webdav-mcp-server .

# Run the container without authentication
docker run -p 3000:3000 \
  -e WEBDAV_ROOT_URL=http://your-webdav-server \
  -e WEBDAV_ROOT_PATH=/webdav \
  webdav-mcp-server
  
# Run the container with authentication for both WebDAV and MCP server
docker run -p 3000:3000 \
  -e WEBDAV_ROOT_URL=http://your-webdav-server \
  -e WEBDAV_ROOT_PATH=/webdav \
  -e WEBDAV_AUTH_ENABLED=true \
  -e WEBDAV_USERNAME=admin \
  -e WEBDAV_PASSWORD=password \
  -e AUTH_ENABLED=true \
  -e AUTH_USERNAME=user \
  -e AUTH_PASSWORD=pass \
  webdav-mcp-server
```

## Configuration

Create a `.env` file in the root directory with the following variables:

```env
# WebDAV configuration
WEBDAV_ROOT_URL=http://localhost:4080
WEBDAV_ROOT_PATH=/webdav

# WebDAV authentication (optional)
WEBDAV_AUTH_ENABLED=true
WEBDAV_USERNAME=admin

# WebDAV password must be plain text (required when auth enabled)
# The WebDAV protocol requires sending the actual password to the server
WEBDAV_PASSWORD=password

# Server configuration (for HTTP mode)
SERVER_PORT=3000

# Authentication configuration for MCP server (optional)
AUTH_ENABLED=true
AUTH_USERNAME=user
AUTH_PASSWORD=pass
AUTH_REALM=MCP WebDAV Server

# Auth password for MCP server can be a bcrypt hash (unlike WebDAV passwords)
# AUTH_PASSWORD={bcrypt}$2y$10$CyLKnUwn9fqqKQFEbxpZFuE9mzWR/x8t6TE7.CgAN0oT8I/5jKJBy
```

### Encrypted Passwords for MCP Server Authentication

For enhanced security of the MCP server (not WebDAV connections), you can use bcrypt-encrypted passwords instead of storing them in plain text:

1. Generate a bcrypt hash:
   ```bash
   # Using the built-in utility
   npm run generate-hash -- yourpassword
   
   # Or with npx
   npx webdav-mcp-generate-hash yourpassword
   ```

2. Add the hash to your .env file with the {bcrypt} prefix:
   ```
   AUTH_PASSWORD={bcrypt}$2y$10$CyLKnUwn9fqqKQFEbxpZFuE9mzWR/x8t6TE7.CgAN0oT8I/5jKJBy
   ```

This way, your MCP server password is stored securely. Note that WebDAV passwords must always be in plain text due to protocol requirements.

## Usage

### Running with stdio transport

This mode is ideal for direct integration with Claude Desktop.

```bash
# If installed globally
webdav-mcp-server

# If using npx
npx webdav-mcp-server

# If built from source
node dist/index.js
```

### Running with HTTP/SSE transport

This mode enables the server to be accessed over HTTP with Server-Sent Events for real-time communication.

```bash
# If installed globally
webdav-mcp-server --http

# If using npx
npx webdav-mcp-server --http

# If built from source
node dist/index.js --http
```

## Quick Start with Docker Compose

The easiest way to get started with both the WebDAV server and the MCP server is to use Docker Compose:

```bash
# Start both WebDAV and MCP servers
cd docker
docker-compose up -d

# This will start:
# - hacdias/webdav server on port 4080 (username: admin, password: admin)
# - MCP server on port 3000 (username: user, password: pass)
```

This setup uses [hacdias/webdav](https://github.com/hacdias/webdav), a simple and standalone WebDAV server written in Go. The configuration for the WebDAV server is stored in `webdav_config.yml`, which you can modify to adjust permissions, add users, or change other settings.

The WebDAV server stores all files in a Docker volume called `webdav_data`, which persists across container restarts.

## WebDAV Server Configuration

The `webdav_config.yml` file configures the hacdias/webdav server used in the Docker Compose setup. Here's what you can customize:

```yaml
# Server address and port
address: 0.0.0.0
port: 6060

# Root data directory
directory: /data

# Enable/disable CORS
cors:
  enabled: true
  # Additional CORS settings...

# Default permissions (C=Create, R=Read, U=Update, D=Delete)
permissions: CRUD

# User definitions
users:
  - username: admin
    password: admin      # Plain text password
    permissions: CRUD    # Full permissions
  
  - username: reader
    password: reader
    permissions: R       # Read-only permissions
    
  # You can also use bcrypt-encrypted passwords
  - username: secure
    password: "{bcrypt}$2y$10$zEP6oofmXFeHaeMfBNLnP.DO8m.H.Mwhd24/TOX2MWLxAExXi4qgi"
```

For more advanced configuration options, refer to the [hacdias/webdav documentation](https://github.com/hacdias/webdav).

## Testing

To run the tests:

```bash
npm test
```

## Integrating with Claude Desktop

1. Ensure the MCP feature is enabled in Claude Desktop

<details>
<summary>Using npx</summary>
2. Open Claude Desktop settings and click edit config (`claude_desktop_config.json`)
3. Add
```json
{
    "mcpServers": {
        "webdav": {
            "command": "npx",
            "args": [
                "-y",
                "webdav-mcp-server"
            ],
            "env": {
                "WEBDAV_ROOT_URL": "<WEBDAV_ROOT_URL>",
                "WEBDAV_ROOT_PATH": "<WEBDAV_ROOT_PATH>",
                "WEBDAV_USERNAME": "<WEBDAV_USERNAME>",
                "WEBDAV_PASSWORD": "<WEBDAV_PASSWORD>",
                "WEBDAV_AUTH_ENABLED": "true|false"
            }
        }
    }
}
```
</details>
<details>
<summary>Using node and local build</summary>
2. Clone this repository and run `setup.sh` on mac/linux or `setup.bat` on windows
3. Open Claude Desktop settings and click edit config (`claude_desktop_config.json`)
4. Add
```json
{
    "mcpServers": {
        "webdav": {
            "command": "node",
            "args": [
                "<path to repository>/dist/index.js"
            ],
            "env": {
                "WEBDAV_ROOT_URL": "<WEBDAV_ROOT_URL>",
                "WEBDAV_ROOT_PATH": "<WEBDAV_ROOT_PATH>",
                "WEBDAV_USERNAME": "<WEBDAV_USERNAME>",
                "WEBDAV_PASSWORD": "<WEBDAV_PASSWORD>",
                "WEBDAV_AUTH_ENABLED": "true|false"
            }
        }
    }
}
```
</details>

## Available MCP Resources

- `webdav://{path}/list` - List files in a directory
- `webdav://{path}/content` - Get file content
- `webdav://{path}/info` - Get file or directory information

## Available MCP Tools

- `webdav_create_remote_file` - Create a new file on a remote WebDAV server
- `webdav_get_remote_file` - Retrieve content from a file stored on a remote WebDAV server
- `webdav_update_remote_file` - Update an existing file on a remote WebDAV server
- `webdav_delete_remote_item` - Delete a file or directory from a remote WebDAV server
- `webdav_create_remote_directory` - Create a new directory on a remote WebDAV server
- `webdav_move_remote_item` - Move or rename a file/directory on a remote WebDAV server
- `webdav_copy_remote_item` - Copy a file/directory to a new location on a remote WebDAV server
- `webdav_list_remote_directory` - List files and directories on a remote WebDAV server

## Available MCP Prompts

- `webdav_create_remote_file` - Prompt to create a new file on a remote WebDAV server
- `webdav_get_remote_file` - Prompt to retrieve content from a remote WebDAV file
- `webdav_update_remote_file` - Prompt to update a file on a remote WebDAV server
- `webdav_delete_remote_item` - Prompt to delete a file/directory from a remote WebDAV server
- `webdav_list_remote_directory` - Prompt to list directory contents on a remote WebDAV server
- `webdav_create_remote_directory` - Prompt to create a directory on a remote WebDAV server
- `webdav_move_remote_item` - Prompt to move/rename a file/directory on a remote WebDAV server
- `webdav_copy_remote_item` - Prompt to copy a file/directory on a remote WebDAV server

## Example Queries in Claude

Here are some example queries you can use in Claude Desktop once the WebDAV MCP server is connected:

- "List files on my remote WebDAV server"
- "Create a new text file called notes.txt on my remote WebDAV server with the following content: Hello World"
- "Get the content of document.txt from my remote WebDAV server"
- "Update config.json on my remote WebDAV server with this new configuration"
- "Create a directory called projects on my remote WebDAV server"
- "Copy report.docx to a backup location on my remote WebDAV server"
- "Move the file old_name.txt to new_name.txt on my remote WebDAV server"
- "Delete temp.txt from my remote WebDAV server"

## Programmatic Usage

You can also use this package programmatically in your own projects:

```javascript
import { startWebDAVServer } from 'webdav-mcp-server';

// For stdio transport without authentication
await startWebDAVServer({
  webdavConfig: {
    rootUrl: 'http://your-webdav-server',
    rootPath: '/webdav',
    authEnabled: false
  },
  useHttp: false
});

// For stdio transport with WebDAV authentication (password must be plain text)
await startWebDAVServer({
  webdavConfig: {
    rootUrl: 'http://your-webdav-server',
    rootPath: '/webdav',
    authEnabled: true,
    username: 'admin',
    password: 'password'
  },
  useHttp: false
});

// With bcrypt hash for MCP server password (HTTP auth only)
await startWebDAVServer({
  webdavConfig: {
    rootUrl: 'http://your-webdav-server',
    rootPath: '/webdav',
    authEnabled: true,
    username: 'admin',
    password: 'password' // WebDAV password must be plain text
  },
  useHttp: true,
  httpConfig: {
    port: 3000,
    auth: {
      enabled: true,
      username: 'user',
      password: '{bcrypt}$2y$10$CyLKnUwn9fqqKQFEbxpZFuE9mzWR/x8t6TE7.CgAN0oT8I/5jKJBy'
    }
  }
});

// For HTTP transport with MCP authentication
await startWebDAVServer({
  webdavConfig: {
    rootUrl: 'http://your-webdav-server',
    rootPath: '/webdav',
    authEnabled: true,
    username: 'admin',
    password: 'password'
  },
  useHttp: true,
  httpConfig: {
    port: 3000,
    auth: {
      enabled: true,
      username: 'user',
      password: 'pass',
      realm: 'MCP WebDAV Server'
    }
  }
});

// For HTTP transport without authentication
await startWebDAVServer({
  webdavConfig: {
    rootUrl: 'http://your-webdav-server',
    rootPath: '/webdav',
    authEnabled: false
  },
  useHttp: true,
  httpConfig: {
    port: 3000,
    auth: {
      enabled: false
    }
  }
});
```

## License

MIT

```

--------------------------------------------------------------------------------
/src/bin.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

// This file serves as the entry point for CLI usage
// It simply imports and executes the main function
import './index.js';

```

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

```dockerfile
FROM node:18-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci

# Copy source files
COPY tsconfig.json ./
COPY src ./src

# Build the application
RUN npm run build

# Expose the default port
EXPOSE 3000

# Set environment variables
ENV NODE_ENV=production

# Run with HTTP transport by default
CMD ["node", "dist/index.js", "--http"]

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "declaration": true,
    "outDir": "dist",
    "rootDir": "src",
    "sourceMap": true
  },
  "include": ["src/**/*.ts" ],
  "exclude": ["node_modules", "dist", "**/*.test.ts" ]
}

```

--------------------------------------------------------------------------------
/docker/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
version: '3.8'

services:
  # WebDAV server using hacdias/webdav
  webdav:
    image: hacdias/webdav:latest
    ports:
      - "4080:6060"
    volumes:
      - webdav_data:/data
      - ./webdav_config.yml:/config.yml:ro
    command: -c /config.yml
    restart: unless-stopped

  # MCP server for WebDAV operations
  mcp-server:
    build:
      context: ../
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - WEBDAV_ROOT_URL=http://webdav:6060
      - WEBDAV_ROOT_PATH=/
      - WEBDAV_AUTH_ENABLED=true
      - WEBDAV_USERNAME=admin
      - WEBDAV_PASSWORD=admin
      - SERVER_PORT=3000
      - AUTH_ENABLED=true
      - AUTH_USERNAME=user
      - AUTH_PASSWORD=pass
    depends_on:
      - webdav
    restart: unless-stopped

volumes:
  webdav_data:

```

--------------------------------------------------------------------------------
/docker/webdav_config.yml:
--------------------------------------------------------------------------------

```yaml
# WebDAV server configuration for use with MCP WebDAV server
address: 0.0.0.0
port: 6060

# Directory that will store the WebDAV files
directory: /data

# Enable debug logging
debug: true

# Enable CORS to allow WebDAV access
cors:
  enabled: true
  credentials: true
  allowed_headers:
    - Depth
    - Authorization
    - Content-Type
    - X-Requested-With
  allowed_hosts:
    - "*"
  allowed_methods:
    - GET
    - POST
    - PUT
    - DELETE
    - PROPFIND
    - PROPPATCH
    - MKCOL
    - COPY
    - MOVE
    - LOCK
    - UNLOCK
  exposed_headers:
    - Content-Length
    - Content-Range

# Define default permissions
permissions: CRUD

# Define users with access to the WebDAV server
users:
  # Admin user with full permissions
  - username: admin
    password: admin
    permissions: CRUD
  
  # Read-only user
  - username: reader
    password: reader
    permissions: R

```

--------------------------------------------------------------------------------
/src/utils/generate-hash.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

/**
 * Utility script to generate a bcrypt hash for a password
 * 
 * Usage:
 *   node generate-hash.js <password>
 * 
 * Example:
 *   node generate-hash.js mySecurePassword
 */

import bcrypt from 'bcryptjs';
import { formatBcryptPassword } from './password-utils.js';

async function main() {
  const args = process.argv.slice(2);
  
  if (args.length === 0) {
    console.error('Error: Password argument is required');
    console.error('Usage: node generate-hash.js <password>');
    process.exit(1);
  }
  
  const password = args[0];
  const rounds = parseInt(args[1] || '10', 10);
  
  try {
    console.log(`Generating bcrypt hash with ${rounds} rounds...`);
    const hash = await bcrypt.hash(password, rounds);
    console.log('\nHash:');
    console.log(hash);
    
    console.log('\nFor .env file:');
    console.log(`WEBDAV_PASSWORD=${formatBcryptPassword(hash)}`);
  } catch (error) {
    console.error('Error generating hash:', error);
    process.exit(1);
  }
}

main();

```

--------------------------------------------------------------------------------
/setup.bat:
--------------------------------------------------------------------------------

```
@echo off
setlocal

echo WebDAV MCP Server Setup
echo =======================
echo.

REM Check if Node.js is installed
where node >nul 2>nul
if %ERRORLEVEL% neq 0 (
    echo Error: Node.js is not installed. Please install Node.js 18 or later.
    exit /b 1
)

REM Check Node.js version
for /f "tokens=1,2,3 delims=v." %%a in ('node -v') do (
    set NODE_MAJOR_VERSION=%%b
)

if %NODE_MAJOR_VERSION% LSS 18 (
    echo Error: Node.js version 18 or higher is required. You have version v%NODE_MAJOR_VERSION%.
    exit /b 1
)

REM Install dependencies
echo Installing dependencies...
call npm install

REM Build the project
echo Building the project...
call npm run build

REM Create .env file if it doesn't exist
if not exist .env (
    echo Creating default .env file...
    copy .env.example .env
    echo Please edit .env file with your WebDAV credentials.
)

echo.
echo Setup completed successfully!
echo.
echo To start the server, run:
echo   npm start         # For stdio transport (Claude Desktop command mode)
echo   npm start -- --http  # For HTTP transport (Claude Desktop HTTP mode)
echo.
echo For more information, see README.md and CLAUDE_INTEGRATION.md

```

--------------------------------------------------------------------------------
/setup.sh:
--------------------------------------------------------------------------------

```bash
#!/bin/bash

# WebDAV MCP Server setup script
echo "WebDAV MCP Server Setup"
echo "======================="
echo

# Check if Node.js is installed
if ! command -v node &> /dev/null; then
    echo "Error: Node.js is not installed. Please install Node.js 18 or later."
    exit 1
fi

# Check Node.js version
NODE_VERSION=$(node -v | cut -d 'v' -f 2)
NODE_MAJOR_VERSION=$(echo $NODE_VERSION | cut -d '.' -f 1)
if [ "$NODE_MAJOR_VERSION" -lt 18 ]; then
    echo "Error: Node.js version 18 or higher is required. You have version $NODE_VERSION."
    exit 1
fi

# Install dependencies
echo "Installing dependencies..."
npm install

# Build the project
echo "Building the project..."
npm run build

# Create .env file if it doesn't exist
if [ ! -f .env ]; then
    echo "Creating default .env file..."
    cp .env.example .env
    echo "Please edit .env file with your WebDAV credentials."
fi

echo
echo "Setup completed successfully!"
echo
echo "To start the server, run:"
echo "  npm start         # For stdio transport (Claude Desktop command mode)"
echo "  npm start -- --http  # For HTTP transport (Claude Desktop HTTP mode)"
echo
echo "For more information, see README.md and CLAUDE_INTEGRATION.md"

```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import 'dotenv/config';
import { startWebDAVServer } from './lib.js';
import { createLogger } from './utils/logger.js';

async function main() {
  try {
    // Get command line arguments
    const args = process.argv.slice(2);
    const useHttp = args.includes('--http');
    
    // Prepare WebDAV config with optional authentication
    const webdavConfig: any = {
      rootUrl: process.env.WEBDAV_ROOT_URL || 'http://localhost:4080',
      rootPath: process.env.WEBDAV_ROOT_PATH || '/',
      authEnabled: process.env.WEBDAV_AUTH_ENABLED === 'true'
    };
    
    // Only add credentials if authentication is enabled
    if (webdavConfig.authEnabled) {
      webdavConfig.username = process.env.WEBDAV_USERNAME;
      webdavConfig.password = process.env.WEBDAV_PASSWORD;
    }
    
    // Prepare HTTP config with optional authentication
    const httpConfig = useHttp ? {
      port: parseInt(process.env.SERVER_PORT || '3000', 10),
      auth: {
        enabled: process.env.AUTH_ENABLED === 'true',
        username: process.env.AUTH_USERNAME,
        password: process.env.AUTH_PASSWORD,
        realm: process.env.AUTH_REALM
      }
    } : undefined;
    
    // Start the server
    await startWebDAVServer({
      webdavConfig,
      useHttp,
      httpConfig
    });
  } catch (error) {
    console.error('Failed to start server:', error);
    process.exit(1);
  }
}

main();

// Export library functions for programmatic usage
export * from './lib.js';

```

--------------------------------------------------------------------------------
/src/middleware/auth-middleware.ts:
--------------------------------------------------------------------------------

```typescript
import { Request, Response, NextFunction } from 'express';
import auth from 'basic-auth';
import { verifyPassword } from '../utils/password-utils.js';

export interface AuthOptions {
  username?: string;
  password?: string;
  realm?: string;
  enabled?: boolean;
}

export function createAuthMiddleware(options: AuthOptions) {
  const {
    username, 
    password, 
    realm = 'MCP WebDAV Server',
    enabled = true
  } = options;

  // If authentication is disabled or credentials are not provided, return a middleware that just calls next()
  if (!enabled || !username || !password) {
    return (req: Request, res: Response, next: NextFunction) => next();
  }

  return async (req: Request, res: Response, next: NextFunction) => {
    const credentials = auth(req);
    
    if (!credentials) {
      res.setHeader('WWW-Authenticate', `Basic realm="${realm}"`);
      res.status(401).send('Unauthorized: Authentication required');
      return;
    }

    // Check username match first
    if (credentials.name !== username) {
      res.setHeader('WWW-Authenticate', `Basic realm="${realm}"`);
      res.status(401).send('Unauthorized: Invalid credentials');
      return;
    }
    
    // Check password using the password utils
    const isPasswordValid = await verifyPassword(credentials.pass, password);
    
    if (!isPasswordValid) {
      res.setHeader('WWW-Authenticate', `Basic realm="${realm}"`);
      res.status(401).send('Unauthorized: Invalid credentials');
      return;
    }
    
    next();
  };
}

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "webdav-mcp-server",
  "version": "1.0.4",
  "description": "A Model Context Protocol server for WebDAV operations with basic authentication",
  "type": "module",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "bin": {
    "webdav-mcp-server": "dist/bin.js",
    "webdav-mcp-generate-hash": "dist/utils/generate-hash.js"
  },
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsc-watch --onSuccess \"node dist/index.js\"",
    "lint": "eslint src --ext .ts",
    "watch": "tsc --watch",
    "prepublishOnly": "npm run build",
    "version:patch": "npm version patch",
    "version:minor": "npm version minor",
    "version:major": "npm version major",
    "publish:patch": "npm run version:patch && npm publish --access=public",
    "publish:minor": "npm run version:minor && npm publish --access=public",
    "publish:major": "npm run version:major && npm publish --access=public",
    "generate-hash": "node dist/utils/generate-hash.js"
  },
  "keywords": [
    "mcp",
    "webdav",
    "model-context-protocol",
    "claude",
    "anthropic",
    "ai",
    "file-management",
    "webdav-client"
  ],
  "author": {
    "name": "WebDAV MCP Server Contributors // Anders Laub"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/LaubPlusCo/mcp-webdav-server"
  },
  "bugs": {
    "url": "https://github.com/LaubPlusCo/mcp-webdav-server/issues"
  },
  "homepage": "https://github.com/LaubPlusCo/mcp-webdav-server/#readme",
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.7.0",
    "basic-auth": "^2.0.1",
    "bcryptjs": "^2.4.3",
    "dotenv": "^16.4.5",
    "express": "^4.19.2",
    "webdav": "^5.8.0",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/basic-auth": "^1.1.7",
    "@types/bcryptjs": "^2.4.6",
    "@types/express": "^4.17.21",
    "@types/jest": "^29.5.12",
    "@types/node": "^20.11.30",
    "@typescript-eslint/eslint-plugin": "^7.2.0",
    "@typescript-eslint/parser": "^7.2.0",
    "eslint": "^8.57.0",
    "typescript": "^5.4.2",
    "tsc-watch": "^6.0.4"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}

```

--------------------------------------------------------------------------------
/NPM_PUBLISH.md:
--------------------------------------------------------------------------------

```markdown
# Publishing to npm

This document provides instructions for publishing the WebDAV MCP Server to npm.

## Prerequisites

1. **npm Account**: Create an account on [npmjs.com](https://www.npmjs.com/) if you don't have one
2. **Login to npm**: Login to your npm account from the command line
   ```bash
   npm login
   ```

## Preparation

1. **Update Version**: The package follows [semantic versioning](https://semver.org/):
   - PATCH (1.0.x): Backward compatible bug fixes
   - MINOR (1.x.0): Backward compatible new features
   - MAJOR (x.0.0): Breaking changes

2. **Test the Package**: Make sure all tests pass
   ```bash
   npm test
   ```

3. **Build the Package**: Ensure the TypeScript compilation works
   ```bash
   npm run build
   ```

## Publishing Workflow

### Option 1: Manual Publishing

1. **Update version in package.json**
2. **Build the project**: `npm run build`
3. **Create a package**: `npm pack`
4. **Test the package**: Install it locally in another project
5. **Publish to npm**: `npm publish --access=public`

### Option 2: Using Scripts

We've added convenience scripts for publishing:

```bash
# Patch version (bug fixes)
npm run publish:patch

# Minor version (new features)
npm run publish:minor

# Major version (breaking changes)
npm run publish:major
```

These scripts will:
1. Update the version number
2. Run tests and build
3. Publish to npm

## Testing Published Package

After publishing, you can test the package by installing it from npm:

```bash
# Install globally
npm install -g webdav-mcp-server

# Or as a dependency in another project
npm install webdav-mcp-server
```

## Notes

- The `prepublishOnly` script runs before publishing to ensure the code is built and tested
- Only the necessary files are included in the package (specified in the `files` field in package.json)
- The `.npmignore` file excludes development-related files from the package

## Troubleshooting

- **Authentication Issues**: If you encounter authentication issues, try `npm logout` and then `npm login` again
- **Versioning Conflicts**: If the version already exists, update the version number in package.json
- **Permission Errors**: Ensure you have the appropriate rights to publish the package (especially if using a scoped package)

```

--------------------------------------------------------------------------------
/src/utils/password-utils.ts:
--------------------------------------------------------------------------------

```typescript
import bcrypt from 'bcryptjs';

/**
 * Get the plain text password from the environment variable value
 * 
 * Supports both plain text passwords and bcrypt hashed passwords in the format:
 * {bcrypt}$2y$05$CyLKnUwn9fqqKQFEbxpZFuE9mzWR/x8t6TE7.CgAN0oT8I/5jKJBy
 * 
 * For bcrypt hashed passwords, we need to return the hash directly as it will be
 * compared with the server's bcrypt hash of the user's input.
 * 
 * @param passwordValue The password value from environment variable
 * @returns The processed password value
 */
export function processPassword(passwordValue: string): string {
    if (!passwordValue) {
        return '';
    }

    const bcryptPrefix = '{bcrypt}';
    
    // If the password starts with {bcrypt}, extract the hash
    if (passwordValue.startsWith(bcryptPrefix)) {
        return passwordValue.substring(bcryptPrefix.length);
    }
    
    // Otherwise, return as is (plain text)
    return passwordValue;
}

/**
 * Check if a password is a bcrypt hash
 * 
 * @param password The password to check
 * @returns True if the password appears to be a bcrypt hash
 */
export function isBcryptHash(password: string): boolean {
    return password.startsWith('$2a$') || 
           password.startsWith('$2b$') || 
           password.startsWith('$2y$');
}

/**
 * Verify a password against a plain text or bcrypt hashed reference
 * 
 * @param input The password input to verify
 * @param reference The reference password (plain text or bcrypt hash)
 * @returns True if the password matches
 */
export async function verifyPassword(input: string, reference: string): Promise<boolean> {
    // If the reference is a bcrypt hash, compare using bcrypt
    if (isBcryptHash(reference)) {
        return bcrypt.compare(input, reference);
    }
    
    // Otherwise, do a simple string comparison
    return input === reference;
}

/**
 * Generate a bcrypt hash for a password
 * 
 * @param password The password to hash
 * @param rounds The number of rounds for bcrypt (default: 10)
 * @returns The bcrypt hash
 */
export async function hashPassword(password: string, rounds: number = 10): Promise<string> {
    return bcrypt.hash(password, rounds);
}

/**
 * Format a password as a bcrypt hash with the {bcrypt} prefix
 * 
 * @param hash The bcrypt hash
 * @returns The formatted password string
 */
export function formatBcryptPassword(hash: string): string {
    return `{bcrypt}${hash}`;
}

```

--------------------------------------------------------------------------------
/src/config/validation.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from 'zod';

// Schema for WebDAV configuration
export const WebDAVConfigSchema = z.object({
  rootUrl: z.string().url('Root URL must be a valid URL'),
  rootPath: z.string().default('/'),
  authEnabled: z.boolean().optional().default(false),
  username: z.string().optional(),
  password: z.string().optional()
})
// Add runtime validation logic
.refine(
  (data) => !data.authEnabled || (data.username && data.password), 
  {
    message: "Username and password are required when authentication is enabled",
    path: ["authEnabled"]
  }
);

// Schema for HTTP server auth configuration
export const HttpAuthConfigSchema = z.object({
  enabled: z.boolean().optional().default(false),
  username: z.string().optional(),
  password: z.string().optional(),
  realm: z.string().optional().default('MCP WebDAV Server')
})
// Add runtime validation logic
.refine(
  (data) => !data.enabled || (data.username && data.password),
  {
    message: "Username and password are required when authentication is enabled",
    path: ["enabled"]
  }
);

// Schema for HTTP server configuration
export const HttpServerConfigSchema = z.object({
  port: z.number().int().positive().default(3000),
  auth: HttpAuthConfigSchema.optional().default({})
});

// Schema for main server options
export const ServerOptionsSchema = z.object({
  webdavConfig: WebDAVConfigSchema,
  useHttp: z.boolean().optional().default(false),
  httpConfig: HttpServerConfigSchema.optional()
})
// Add runtime validation logic
.refine(
  (data) => !data.useHttp || data.httpConfig !== undefined,
  {
    message: "HTTP configuration is required when useHttp is true",
    path: ["useHttp"]
  }
);

// Export types based on the schemas
export type ValidatedWebDAVConfig = z.infer<typeof WebDAVConfigSchema>;
export type ValidatedHttpAuthConfig = z.infer<typeof HttpAuthConfigSchema>;
export type ValidatedHttpServerConfig = z.infer<typeof HttpServerConfigSchema>;
export type ValidatedServerOptions = z.infer<typeof ServerOptionsSchema>;

/**
 * Validate server configuration options
 * @param options Server configuration options
 * @returns Validated options or throws error
 */
export function validateConfig(options: any): ValidatedServerOptions {
  try {
    return ServerOptionsSchema.parse(options);
  } catch (error) {
    if (error instanceof z.ZodError) {
      // Format error message for better readability
      const formattedErrors = error.errors.map(err => 
        `${err.path.join('.')}: ${err.message}`
      ).join('\n');
      
      throw new Error(`Configuration validation failed:\n${formattedErrors}`);
    }
    throw error;
  }
}

```

--------------------------------------------------------------------------------
/src/utils/logger.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Logger utility that uses the MCP SDK's logging mechanism
 */

import { Server } from '@modelcontextprotocol/sdk/server/index.js';

// Global server reference for logging
let globalMcpServer: Server | null = null;

/**
 * Set the global MCP server instance for all loggers to use
 */
export function setLoggerServer(server: Server): void {
  globalMcpServer = server;
}

interface LoggerOptions {
  server?: Server;
}

/**
 * Logger class that uses MCP SDK for logging
 */
export class Logger {
  private context: string;
  private server: Server | null;

  constructor(context: string, options: LoggerOptions = {}) {
    this.context = context;
    this.server = options.server || globalMcpServer;
  }

  /**
   * Check if logging is supported by the server
   */
  private isLoggingSupported(): boolean {
    if (!this.server) return false;

    // Check if client has registered the logging capability
    const capabilities = this.server.getClientCapabilities?.();
    return capabilities?.logging !== undefined;
  }

  /**
   * Format data for logging
   */
  private formatData(message: string, data?: any): any {
    if (data === undefined) {
      return message;
    }
    
    try {
      // Handle various data types
      if (typeof data === 'string') {
        return `${message} ${data}`;
      } else if (data instanceof Error) {
        return `${message} ${data.message}\n${data.stack || ''}`;
      } else {
        return {
          message,
          data
        };
      }
    } catch (err) {
      return `${message} [Error formatting log data: ${err}]`;
    }
  }

  /**
   * Safely send a logging message to the server
   */
  private sendLogMessage(level: string, message: string, data?: any): void {
    if (!this.server || !this.isLoggingSupported()) return;
    
    try {
      this.server.sendLoggingMessage({
        level: level as any,
        logger: this.context,
        data: this.formatData(message, data)
      }).catch(() => {
        // Silently ignore any errors
      });
    } catch {
      // Silently ignore any errors
    }
  }

  /**
   * Log an error message
   */
  error(message: string, data?: any): void {
    this.sendLogMessage('error', message, data);
  }

  /**
   * Log a warning message
   */
  warn(message: string, data?: any): void {
    this.sendLogMessage('warning', message, data);
  }

  /**
   * Log an info message
   */
  info(message: string, data?: any): void {
    this.sendLogMessage('info', message, data);
  }

  /**
   * Log a debug message
   */
  debug(message: string, data?: any): void {
    this.sendLogMessage('debug', message, data);
  }
}

/**
 * Create a new logger with the given context
 */
export function createLogger(context: string, options: LoggerOptions = {}): Logger {
  return new Logger(context, options);
}

```

--------------------------------------------------------------------------------
/PASSWORD_ENCRYPTION.md:
--------------------------------------------------------------------------------

```markdown
# WebDAV Password Encryption

This document explains how to use bcrypt-encrypted passwords with the WebDAV MCP Server.

## Why Use Encrypted Passwords?

When connecting to a WebDAV server, you need to provide authentication credentials. Storing these credentials in plain text in your environment variables or configuration files poses a security risk. By using bcrypt-encrypted passwords:

1. Your WebDAV password is never stored in plain text
2. The hash cannot be easily reversed to obtain the original password
3. Even if your .env file or configuration is exposed, your actual WebDAV password remains protected

## How It Works

The WebDAV MCP Server supports bcrypt-hashed passwords using the following format:

```
{bcrypt}$2y$10$CyLKnUwn9fqqKQFEbxpZFuE9mzWR/x8t6TE7.CgAN0oT8I/5jKJBy
```

When the server initializes:
1. It detects if the `WEBDAV_PASSWORD` environment variable starts with `{bcrypt}`
2. If it does, it extracts the bcrypt hash (everything after the prefix)
3. The hash is then passed to the WebDAV client for authentication

## Generating a Bcrypt Hash

You can generate a bcrypt hash for your password using the built-in utility:

```bash
# Using the npm script
npm run generate-hash -- yourpassword [rounds]

# Or with npx
npx webdav-mcp-generate-hash yourpassword [rounds]

# Or directly, if built from source
node dist/utils/generate-hash.js yourpassword [rounds]
```

The optional `rounds` parameter (default: 10) determines the computational complexity of the hash. Higher values are more secure but slower to compute.

## Adding to Your Environment

Once you have generated the hash, add it to your `.env` file:

```env
WEBDAV_PASSWORD={bcrypt}$2y$10$CyLKnUwn9fqqKQFEbxpZFuE9mzWR/x8t6TE7.CgAN0oT8I/5jKJBy
```

Or when using Docker:

```bash
docker run -p 3000:3000 \
  -e WEBDAV_USERNAME=admin \
  -e WEBDAV_PASSWORD="{bcrypt}$2y$10$CyLKnUwn9fqqKQFEbxpZFuE9mzWR/x8t6TE7.CgAN0oT8I/5jKJBy" \
  webdav-mcp-server
```

## Security Considerations

1. **Hash Strength**: Use at least 10 rounds for bcrypt (the default) to ensure adequate security
2. **Environment Security**: Even with encrypted passwords, keep your .env file secure
3. **Transport Security**: Use HTTPS when connecting to remote WebDAV servers
4. **Password Rotation**: Periodically update your passwords and their corresponding hashes

## Programmatic Usage

When using the WebDAV MCP Server programmatically, you can provide encrypted passwords the same way:

```javascript
import { startWebDAVServer } from 'webdav-mcp-server';

await startWebDAVServer({
  webdavConfig: {
    rootUrl: 'https://your-webdav-server',
    rootPath: '/webdav',
    username: 'admin',
    password: '{bcrypt}$2y$10$CyLKnUwn9fqqKQFEbxpZFuE9mzWR/x8t6TE7.CgAN0oT8I/5jKJBy'
  },
  useHttp: false
});
```

## Troubleshooting

If you experience authentication issues:

1. Verify that your bcrypt hash is correct and properly formatted
2. Ensure you're using the correct prefix: `{bcrypt}`
3. Check that your WebDAV server supports basic authentication
4. Verify that the hashed password corresponds to the correct plaintext password

```

--------------------------------------------------------------------------------
/src/servers/express-server.ts:
--------------------------------------------------------------------------------

```typescript
import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { createAuthMiddleware, AuthOptions } from '../middleware/auth-middleware.js';
import { createLogger } from '../utils/logger.js';

export interface ExpressServerConfig {
  port: number;
  auth?: {
    username?: string;
    password?: string;
    realm?: string;
    enabled?: boolean;
  };
}

export function setupExpressServer(server: McpServer, config: ExpressServerConfig): express.Application {
  // Create logger using the server instance
  const logger = createLogger('ExpressServer');
  const app = express();
  app.use(express.json());

  // Map to store connected clients
  const clients = new Map<string, SSEServerTransport>();

  // Create auth middleware based on configuration
  const authOptions: AuthOptions = {
    username: config.auth?.username || process.env.AUTH_USERNAME,
    password: config.auth?.password || process.env.AUTH_PASSWORD,
    realm: config.auth?.realm || process.env.AUTH_REALM || 'MCP WebDAV Server',
    enabled: config.auth?.enabled ?? (process.env.AUTH_ENABLED === 'true')
  };

  // Only apply auth middleware if enabled
  if (authOptions.enabled && authOptions.username && authOptions.password) {
    const authMiddleware = createAuthMiddleware(authOptions);
    app.use(authMiddleware);
    logger.info('Authentication middleware enabled');
  } else {
    logger.info('Authentication middleware disabled');
  }

  // SSE endpoint for client connection
  app.get('/sse', async (req, res) => {
    // Create transport for this client
    const transport = new SSEServerTransport('/messages', res);
    
    // Store the transport by its session ID
    clients.set(transport.sessionId, transport);
    
    // Start the SSE connection
    await transport.start();
    
    // Connect the server to this transport
    server.connect(transport).catch(error => {
      logger.error(`Error connecting server to transport:`, error);
    });
    
    // Handle client disconnect
    req.on('close', () => {
      clients.delete(transport.sessionId);
      logger.info(`Client ${transport.sessionId} disconnected`);
    });
  });

  // Message endpoint for client to server communication
  app.post('/messages', async (req, res) => {
    const sessionId = req.query.sessionId as string;
    
    if (!sessionId || !clients.has(sessionId)) {
      res.status(400).json({ error: 'Invalid or missing session ID' });
      return;
    }
    
    const transport = clients.get(sessionId)!;
    
    try {
      await transport.handlePostMessage(req, res);
    } catch (error) {
      logger.error(`Error handling message for session ${sessionId}:`, error);
      // Note: handlePostMessage already sends appropriate response
    }
  });

  // Health check endpoint
  app.get('/health', (req, res) => {
    res.json({
      status: 'ok',
      name: 'WebDAV MCP Server',
      version: '1.0.0',
      description: 'MCP Server for WebDAV operations with basic authentication',
      connectedClients: clients.size
    });
  });

  // Start the server
  app.listen(config.port, () => {
    logger.info(`HTTP server with SSE transport listening on port ${config.port}`);
  });

  return app;
}

```

--------------------------------------------------------------------------------
/src/lib.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

import { WebDAVService, WebDAVConfig } from './services/webdav-service.js';
import { setupResourceHandlers } from './handlers/resource-handlers.js';
import { setupToolHandlers } from './handlers/tool-handlers.js';
import { setupPromptHandlers } from './handlers/prompt-handlers.js';
import { setupExpressServer } from './servers/express-server.js';
import { validateConfig, ValidatedServerOptions } from './config/validation.js';
import { createLogger, setLoggerServer } from './utils/logger.js';
import { webdavConnectionPool } from './services/webdav-connection-pool.js';

// Logger will be initialized after server creation

export interface HttpServerConfig {
  port: number;
  auth?: {
    username?: string;
    password?: string;
    realm?: string;
    enabled?: boolean;
  };
}

export interface ServerOptions {
  webdavConfig: WebDAVConfig;
  useHttp?: boolean;
  httpConfig?: HttpServerConfig;
}

/**
 * Start the WebDAV MCP Server with the provided configuration
 * 
 * @param options Server configuration options
 * @returns A promise that resolves when the server is started
 */
export async function startWebDAVServer(options: ServerOptions): Promise<void> {
  try {
    // Validate the configuration
    const validatedOptions = validateConfig(options);
    const { webdavConfig, useHttp, httpConfig } = validatedOptions;
    
    // Initialize the WebDAV service
    const webdavService = new WebDAVService(webdavConfig);

    // Create the MCP server
    const server = new McpServer({
      name: 'WebDAV Server',
      version: '1.0.1',
      description: 'MCP Server for WebDAV operations with configurable authentication',
      capabilities: {
        logging: {},     // Support for logging
        prompts: {},     // Support for prompts
        resources: {},   // Support for resources
        tools: {}        // Support for tools
      }
    });
    
    // Set the MCP server for all loggers to use
    setLoggerServer(server.server);
    
    // Now that the server is set up, we can create a logger
    const logger = createLogger('WebDAVServer');
    
    // Log startup information
    logger.info('WebDAV MCP Server started', {
      webdavUrl: webdavConfig.rootUrl,
      webdavAuthEnabled: webdavConfig.authEnabled,
      useHttp,
      httpPort: useHttp ? httpConfig?.port : undefined,
      httpAuthEnabled: useHttp ? httpConfig?.auth?.enabled : undefined
    });
    
    // Set up handlers
    logger.debug('Setting up MCP handlers');
    
    setupResourceHandlers(server, webdavService);
    setupToolHandlers(server, webdavService);
    setupPromptHandlers(server);

    // Get connection pool stats for logging
    const poolStats = webdavConnectionPool.getStats();
    logger.info('WebDAV connection pool status', poolStats);

    if (useHttp) {
      // Start Express server with SSE transport
      logger.info(`Starting HTTP server on port ${httpConfig!.port}`);
      setupExpressServer(server, {
        port: httpConfig!.port,
        auth: httpConfig!.auth
      });
      logger.info(`HTTP server started on port ${httpConfig!.port}`);
    } else {
      // Use stdio transport
      logger.info('Starting server with stdio transport');
      const transport = new StdioServerTransport();
      await server.connect(transport);
      logger.info('Server connected with stdio transport');
    }
  } catch (error) {
    console.error('Failed to start server:', error);
    throw error;
  }
}

// Re-export types
export { WebDAVConfig, WebDAVService } from './services/webdav-service.js';

```

--------------------------------------------------------------------------------
/src/handlers/resource-handlers.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { WebDAVService } from '../services/webdav-service.js';

export function setupResourceHandlers(server: McpServer, webdavService: WebDAVService) {
  // List files in a directory
  server.resource(
    'webdav_list_remote_directory',
    new ResourceTemplate('webdav://{path}/list', {
      // The list property expects a proper response format
      list: async () => {
        return {
          resources: [
            {
              uri: 'webdav://',
              name: 'WebDAV Root',
              description: 'Access to WebDAV resources'
            }
          ]
        };
      }
    }),
    async (uri, { path }) => {
      try {
        const normalizedPath = path ? String(path) : '/';
        const files = await webdavService.list(normalizedPath);
        
        // Format the file listing for display
        const content = files.map(file => {
          const type = file.type === 'directory' ? 'Directory' : 'File';
          const size = file.type === 'file' ? `Size: ${formatSize(file.size)}` : '';
          const lastMod = file.lastmod ? `Last Modified: ${file.lastmod}` : '';
          
          return `${type}: ${file.basename}
${size}
${lastMod}
Path: ${file.filename}
${'-'.repeat(40)}`;
        }).join('\n');
        
        return {
          contents: [{
            uri: uri.href,
            text: content ? content : 'Empty directory'
          }]
        };
      } catch (error) {
        return {
          contents: [{
            uri: uri.href,
            text: `Error listing directory: ${(error as Error).message}`
          }]
        };
      }
    }
  );

  // Get file content
  server.resource(
    'webdav_get_remote__file',
    new ResourceTemplate('webdav://{path}/content', {
      list: undefined,
    }),
    async (uri, { path }) => {
      try {
        if (!path) {
          throw new Error('Path parameter is required');
        }
        
        // Ensure path is treated as a string
        const pathString = String(path);
        const content = await webdavService.readFile(pathString);
        
        return {
          contents: [{
            uri: uri.href,
            text: content
          }]
        };
      } catch (error) {
        return {
          contents: [{
            uri: uri.href,
            text: `Error reading file: ${(error as Error).message}`
          }]
        };
      }
    }
  );

  // Get file or directory info
  server.resource(
    'webdav_get_remote_info',
    new ResourceTemplate('webdav://{path}/info', {
      list: undefined,
    }),
    async (uri, { path }) => {
      try {
        if (!path) {
          throw new Error('Path parameter is required');
        }
        
        // Ensure path is treated as a string
        const pathString = String(path);
        const stat = await webdavService.stat(pathString);
        
        // Format the file information
        const info = [
          `Name: ${stat.basename}`,
          `Type: ${stat.type}`,
          `Path: ${stat.filename}`,
          stat.type === 'file' ? `Size: ${formatSize(stat.size)}` : '',
          stat.lastmod ? `Last Modified: ${stat.lastmod}` : '',
          `Mime Type: ${stat.mime || 'unknown'}`
        ].filter(Boolean).join('\n');
        
        return {
          contents: [{
            uri: uri.href,
            text: info
          }]
        };
      } catch (error) {
        return {
          contents: [{
            uri: uri.href,
            text: `Error getting info: ${(error as Error).message}`
          }]
        };
      }
    }
  );
}

// Helper function to format file size
function formatSize(bytes: number | undefined): string {
  if (bytes === undefined) return 'Unknown';
  
  const units = ['B', 'KB', 'MB', 'GB', 'TB'];
  let size = bytes;
  let unitIndex = 0;
  
  while (size >= 1024 && unitIndex < units.length - 1) {
    size /= 1024;
    unitIndex++;
  }
  
  return `${size.toFixed(2)} ${units[unitIndex]}`;
}

```

--------------------------------------------------------------------------------
/src/handlers/prompt-handlers.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';

export function setupPromptHandlers(server: McpServer) {
  // Prompt for creating a new file
  server.prompt(
    'webdav_create_remote_file',
    {
      path: z.string().min(1, 'Path must not be empty'),
      content: z.string(),
      description: z.string().optional()
    },
    (args) => ({
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `Create a new file on the remote WebDAV server at path "${args.path}"${args.description ? ` with the following description: ${args.description}` : ''}.

Content to save on the remote WebDAV server:
${args.content}

Please execute this remote WebDAV operation and confirm when complete.`
          }
        }
      ]
    })
  );

  // Prompt for reading a file
  server.prompt(
    'webdav_get_remote_file',
    {
      path: z.string().min(1, 'Path must not be empty')
    },
    (args) => ({
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `Retrieve the content of the file located at "${args.path}" from the remote WebDAV server and display its contents.`
          }
        }
      ]
    })
  );

  // Prompt for updating a file
  server.prompt(
    'webdav_update_remote_file',
    {
      path: z.string().min(1, 'Path must not be empty'),
      content: z.string(),
      reason: z.string().optional()
    },
    (args) => ({
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `Update the existing file at "${args.path}" on the remote WebDAV server${args.reason ? ` for the following reason: ${args.reason}` : ''}.

New content to save on the remote WebDAV server:
${args.content}

Please execute this remote WebDAV update operation and confirm when complete.`
          }
        }
      ]
    })
  );

  // Prompt for deleting a file or directory
  server.prompt(
    'webdav_delete_remote_item',
    // The issue is with boolean not being compatible with the prompt schema
    // Using string as a workaround
    {
      path: z.string().min(1, 'Path must not be empty'),
      confirm: z.string().optional()
    },
    (args) => {
      const confirmationEnabled = args.confirm !== 'false';
      const pathValue = args.path;
      const isDirectory = pathValue.endsWith('/');
      
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Delete the ${isDirectory ? 'directory' : 'file'} at "${pathValue}" from the remote WebDAV server.
${confirmationEnabled ? 'Please confirm this action to proceed with deletion.' : 'Execute this remote deletion operation.'}

Please confirm when the remote WebDAV deletion is complete.`
            }
          }
        ]
      };
    }
  );

  // Prompt for listing directory contents
  server.prompt(
    'webdav_list_remote_directory',
    {
      path: z.string().optional()
    },
    (args) => {
      const pathToUse = args.path || '/';
      
      return {
        messages: [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `List all files and directories in the remote WebDAV directory "${pathToUse}".

Please provide a well-formatted list showing:
- File/directory names
- Types (file or directory)
- Sizes (for files)
- Last modified dates (if available)

This is for a remote WebDAV server, not a local filesystem.`
            }
          }
        ]
      };
    }
  );

  // Prompt for creating a directory
  server.prompt(
    'webdav_create_remote_directory',
    {
      path: z.string().min(1, 'Path must not be empty')
    },
    (args) => ({
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `Create a new directory on the remote WebDAV server at path "${args.path}".

Please execute this remote WebDAV operation and confirm when the directory has been created.`
          }
        }
      ]
    })
  );

  // Prompt for moving/renaming a file or directory
  server.prompt(
    'webdav_move_remote_item',
    {
      fromPath: z.string().min(1, 'Source path must not be empty'),
      toPath: z.string().min(1, 'Destination path must not be empty'),
      reason: z.string().optional()
    },
    (args) => ({
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `Move or rename the file/directory from "${args.fromPath}" to "${args.toPath}" on the remote WebDAV server${args.reason ? ` for the following reason: ${args.reason}` : ''}.

Please execute this remote WebDAV operation and confirm when complete.`
          }
        }
      ]
    })
  );

  // Prompt for copying a file or directory
  server.prompt(
    'webdav_copy_remote_item',
    {
      fromPath: z.string().min(1, 'Source path must not be empty'),
      toPath: z.string().min(1, 'Destination path must not be empty')
    },
    (args) => ({
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `Copy the file or directory from "${args.fromPath}" to "${args.toPath}" on the remote WebDAV server.

Please execute this remote WebDAV copy operation and confirm when complete.`
          }
        }
      ]
    })
  );
}
```

--------------------------------------------------------------------------------
/src/services/webdav-connection-pool.ts:
--------------------------------------------------------------------------------

```typescript
import { createClient, WebDAVClient, AuthType } from 'webdav';
import { createLogger } from '../utils/logger.js';

const logger = createLogger('WebDAVConnectionPool');

interface WebDAVConnectionOptions {
  rootUrl: string;
  username?: string;
  password?: string;
  authEnabled?: boolean;
}

interface PooledConnection {
  client: WebDAVClient;
  lastUsed: number;
  connectionKey: string;
}

/**
 * WebDAV Connection Pool for reusing connections
 */
export class WebDAVConnectionPool {
  private connections: Map<string, PooledConnection> = new Map();
  private cleanupInterval: NodeJS.Timeout;
  private maxIdleTimeMs: number;
  private maxConnections: number;

  /**
   * Create a new WebDAV connection pool
   * 
   * @param options Pool configuration options
   */
  constructor(options: {
    maxIdleTimeMs?: number; // Maximum time in ms a connection can be idle before being removed
    maxConnections?: number; // Maximum number of connections to keep in the pool
    cleanupIntervalMs?: number; // How often to check for idle connections
  } = {}) {
    this.maxIdleTimeMs = options.maxIdleTimeMs || 5 * 60 * 1000; // Default: 5 minutes
    this.maxConnections = options.maxConnections || 10; // Default: 10 connections
    const cleanupIntervalMs = options.cleanupIntervalMs || 60 * 1000; // Default: 1 minute
    
    // Start cleanup interval
    this.cleanupInterval = setInterval(() => {
      this.cleanupIdleConnections();
    }, cleanupIntervalMs);
    
    // Ensure cleanup happens even if the process exits
    process.on('exit', () => {
      this.destroy();
    });
  }

  /**
   * Generate a unique key for a connection based on its options
   * 
   * @param options Connection options
   * @returns Connection key
   */
  private generateConnectionKey(options: WebDAVConnectionOptions): string {
    const { rootUrl, authEnabled, username } = options;
    // Include authentication details in key only if auth is enabled
    return authEnabled 
      ? `${rootUrl}:${authEnabled}:${username}`
      : `${rootUrl}:${authEnabled}`;
  }

  /**
   * Get a WebDAV client from the pool or create a new one
   * 
   * @param options Connection options
   * @returns WebDAV client
   */
  getConnection(options: WebDAVConnectionOptions): WebDAVClient {
    const connectionKey = this.generateConnectionKey(options);
    
    // Check if we have a connection in the pool
    if (this.connections.has(connectionKey)) {
      const connection = this.connections.get(connectionKey)!;
      // Update last used timestamp
      connection.lastUsed = Date.now();
      logger.debug(`Reusing existing WebDAV connection: ${connectionKey}`);
      return connection.client;
    }
    
    // If we've reached the max connections, remove the oldest one
    if (this.connections.size >= this.maxConnections) {
      this.removeOldestConnection();
    }
    
    // Create new client
    logger.debug(`Creating new WebDAV connection: ${connectionKey}`);
    const client = this.createClient(options);
    
    // Add to pool
    this.connections.set(connectionKey, {
      client,
      lastUsed: Date.now(),
      connectionKey
    });
    
    return client;
  }

  /**
   * Create a new WebDAV client
   * 
   * @param options Connection options
   * @returns WebDAV client
   */
  private createClient(options: WebDAVConnectionOptions): WebDAVClient {
    const { rootUrl, authEnabled, username, password } = options;
    
    if (authEnabled && username && password) {
      // Create authenticated client with plain text password
      logger.debug(`Creating authenticated WebDAV client for ${rootUrl}`);
      return createClient(rootUrl, {
        authType: AuthType.Password,
        username,
        password // Password must be in plain text for WebDAV authentication
      });
    } else {
      // Create unauthenticated client
      logger.debug(`Creating unauthenticated WebDAV client for ${rootUrl}`);
      return createClient(rootUrl, {
        authType: AuthType.None
      });
    }
  }

  /**
   * Remove the oldest connection from the pool
   */
  private removeOldestConnection(): void {
    if (this.connections.size === 0) return;
    
    let oldestKey = '';
    let oldestTime = Infinity;
    
    // Find the oldest connection
    for (const [key, connection] of this.connections.entries()) {
      if (connection.lastUsed < oldestTime) {
        oldestTime = connection.lastUsed;
        oldestKey = key;
      }
    }
    
    if (oldestKey) {
      logger.debug(`Removing oldest connection: ${oldestKey}`);
      this.connections.delete(oldestKey);
    }
  }

  /**
   * Clean up idle connections
   */
  private cleanupIdleConnections(): void {
    const now = Date.now();
    let removedCount = 0;
    
    for (const [key, connection] of this.connections.entries()) {
      const idleTime = now - connection.lastUsed;
      
      if (idleTime > this.maxIdleTimeMs) {
        logger.debug(`Removing idle connection: ${key}, idle for ${idleTime}ms`);
        this.connections.delete(key);
        removedCount++;
      }
    }
    
    if (removedCount > 0) {
      logger.info(`Cleaned up ${removedCount} idle WebDAV connections, ${this.connections.size} remaining`);
    }
  }

  /**
   * Get statistics about the connection pool
   */
  getStats() {
    return {
      activeConnections: this.connections.size,
      maxConnections: this.maxConnections,
      connectionKeys: Array.from(this.connections.keys())
    };
  }

  /**
   * Destroy the connection pool and clean up resources
   */
  destroy(): void {
    clearInterval(this.cleanupInterval);
    this.connections.clear();
    logger.info('WebDAV connection pool destroyed');
  }
}

// Create and export a singleton instance
export const webdavConnectionPool = new WebDAVConnectionPool();

```

--------------------------------------------------------------------------------
/src/handlers/tool-handlers.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { WebDAVService } from '../services/webdav-service.js';
import { z } from 'zod';

export function setupToolHandlers(server: McpServer, webdavService: WebDAVService) {
  // Create file tool
  server.tool(
    'webdav_create_remote_file',
    'Create a new file on a remote WebDAV server at the specified path',
    {
      path: z.string().min(1, 'Path must not be empty'),
      content: z.string(),
      overwrite: z.boolean().optional().default(false)
    },
    async ({ path, content, overwrite }) => {
      try {
        // Check if file exists and respect overwrite flag
        const exists = await webdavService.exists(path);
        if (exists && !overwrite) {
          return {
            content: [{
              type: 'text',
              text: `Error: File already exists at ${path}. Use overwrite=true to replace it.`
            }],
            isError: true
          };
        }

        await webdavService.writeFile(path, content);
        
        return {
          content: [{
            type: 'text',
            text: `File created successfully at ${path}`
          }]
        };
      } catch (error) {
        return {
          content: [{
            type: 'text',
            text: `Error creating file: ${(error as Error).message}`
          }],
          isError: true
        };
      }
    }
  );

  // Read file tool
  server.tool(
    'webdav_get_remote_file',
    'Retrieve content from a file stored on a remote WebDAV server',
    {
      path: z.string().min(1, 'Path must not be empty')
    },
    async ({ path }) => {
      try {
        const content = await webdavService.readFile(path);
        
        return {
          content: [{
            type: 'text',
            text: content
          }]
        };
      } catch (error) {
        return {
          content: [{
            type: 'text',
            text: `Error reading file: ${(error as Error).message}`
          }],
          isError: true
        };
      }
    }
  );

  // Update file tool
  server.tool(
    'webdav_update_remote_file',
    'Update an existing file on a remote WebDAV server with new content',
    {
      path: z.string().min(1, 'Path must not be empty'),
      content: z.string()
    },
    async ({ path, content }) => {
      try {
        // Check if file exists
        const exists = await webdavService.exists(path);
        if (!exists) {
          return {
            content: [{
              type: 'text',
              text: `Error: File does not exist at ${path}`
            }],
            isError: true
          };
        }

        await webdavService.writeFile(path, content);
        
        return {
          content: [{
            type: 'text',
            text: `File updated successfully at ${path}`
          }]
        };
      } catch (error) {
        return {
          content: [{
            type: 'text',
            text: `Error updating file: ${(error as Error).message}`
          }],
          isError: true
        };
      }
    }
  );

  // Delete file or directory tool
  server.tool(
    'webdav_delete_remote_item',
    'Delete a file or directory from a remote WebDAV server',
    {
      path: z.string().min(1, 'Path must not be empty')
    },
    async ({ path }) => {
      try {
        // Check if path exists
        const exists = await webdavService.exists(path);
        if (!exists) {
          return {
            content: [{
              type: 'text',
              text: `Error: Path does not exist at ${path}`
            }],
            isError: true
          };
        }

        await webdavService.delete(path);
        
        return {
          content: [{
            type: 'text',
            text: `Successfully deleted ${path}`
          }]
        };
      } catch (error) {
        return {
          content: [{
            type: 'text',
            text: `Error deleting: ${(error as Error).message}`
          }],
          isError: true
        };
      }
    }
  );

  // Create directory tool
  server.tool(
    'webdav_create_remote_directory',
    'Create a new directory on a remote WebDAV server',
    {
      path: z.string().min(1, 'Path must not be empty')
    },
    async ({ path }) => {
      try {
        await webdavService.createDirectory(path);
        
        return {
          content: [{
            type: 'text',
            text: `Directory created successfully at ${path}`
          }]
        };
      } catch (error) {
        return {
          content: [{
            type: 'text',
            text: `Error creating directory: ${(error as Error).message}`
          }],
          isError: true
        };
      }
    }
  );

  // Move/rename file or directory tool
  server.tool(
    'webdav_move_remote_item',
    'Move or rename a file or directory on a remote WebDAV server',
    {
      fromPath: z.string().min(1, 'Source path must not be empty'),
      toPath: z.string().min(1, 'Destination path must not be empty'),
      overwrite: z.boolean().optional().default(false)
    },
    async ({ fromPath, toPath, overwrite }) => {
      try {
        // Check if source exists
        const sourceExists = await webdavService.exists(fromPath);
        if (!sourceExists) {
          return {
            content: [{
              type: 'text',
              text: `Error: Source path does not exist at ${fromPath}`
            }],
            isError: true
          };
        }

        // Check if destination exists and respect overwrite flag
        const destExists = await webdavService.exists(toPath);
        if (destExists && !overwrite) {
          return {
            content: [{
              type: 'text',
              text: `Error: Destination already exists at ${toPath}. Use overwrite=true to replace it.`
            }],
            isError: true
          };
        }

        await webdavService.move(fromPath, toPath);
        
        return {
          content: [{
            type: 'text',
            text: `Successfully moved ${fromPath} to ${toPath}`
          }]
        };
      } catch (error) {
        return {
          content: [{
            type: 'text',
            text: `Error moving: ${(error as Error).message}`
          }],
          isError: true
        };
      }
    }
  );

  // Copy file or directory tool
  server.tool(
    'webdav_copy_remote_item',
    'Copy a file or directory to a new location on a remote WebDAV server',
    {
      fromPath: z.string().min(1, 'Source path must not be empty'),
      toPath: z.string().min(1, 'Destination path must not be empty'),
      overwrite: z.boolean().optional().default(false)
    },
    async ({ fromPath, toPath, overwrite }) => {
      try {
        // Check if source exists
        const sourceExists = await webdavService.exists(fromPath);
        if (!sourceExists) {
          return {
            content: [{
              type: 'text',
              text: `Error: Source path does not exist at ${fromPath}`
            }],
            isError: true
          };
        }

        // Check if destination exists and respect overwrite flag
        const destExists = await webdavService.exists(toPath);
        if (destExists && !overwrite) {
          return {
            content: [{
              type: 'text',
              text: `Error: Destination already exists at ${toPath}. Use overwrite=true to replace it.`
            }],
            isError: true
          };
        }

        await webdavService.copy(fromPath, toPath);
        
        return {
          content: [{
            type: 'text',
            text: `Successfully copied ${fromPath} to ${toPath}`
          }]
        };
      } catch (error) {
        return {
          content: [{
            type: 'text',
            text: `Error copying: ${(error as Error).message}`
          }],
          isError: true
        };
      }
    }
  );

  // List directory tool
  server.tool(
    'webdav_list_remote_directory',
    'List files and directories at the specified path on a remote WebDAV server',
    {
      path: z.string().optional().default('/')
    },
    async ({ path }) => {
      try {
        const files = await webdavService.list(path);
        
        // Format response
        const formattedFiles = files.map(file => ({
          name: file.basename,
          path: file.filename,
          type: file.type,
          size: file.size,
          lastModified: file.lastmod
        }));
        
        return {
          content: [{
            type: 'text',
            text: JSON.stringify(formattedFiles, null, 2)
          }]
        };
      } catch (error) {
        return {
          content: [{
            type: 'text',
            text: `Error listing directory: ${(error as Error).message}`
          }],
          isError: true
        };
      }
    }
  );
}
```

--------------------------------------------------------------------------------
/src/services/webdav-service.ts:
--------------------------------------------------------------------------------

```typescript
import { WebDAVClient } from 'webdav';
import { webdavConnectionPool } from './webdav-connection-pool.js';
import { createLogger } from '../utils/logger.js';

const logger = createLogger('WebDAVService');

// Define our own FileStat interface to match what we use in the application
export interface FileStat {
  filename: string;
  basename: string;
  lastmod?: string;
  size?: number;
  type: 'file' | 'directory';
  mime?: string;
  [key: string]: any;
}

// Define interfaces for response types
interface ResponseData {
  status?: number;
  data?: any;
  [key: string]: any;
}

export interface WebDAVConfig {
  rootUrl: string;
  rootPath: string;
  username?: string;
  password?: string;
  authEnabled?: boolean;
}

export class WebDAVService {
  private client: WebDAVClient;
  private rootPath: string;

  constructor(config: WebDAVConfig) {
    logger.debug('Initializing WebDAV service', { rootUrl: config.rootUrl, rootPath: config.rootPath });
    
    // Determine if auth is enabled
    const authEnabled = Boolean(config.authEnabled) || Boolean(config.username && config.password);
    
    // Get connection options
    const connectionOptions: any = {
      rootUrl: config.rootUrl,
      authEnabled,
      username: config.username,
      password: config.password
    };
    
    // Get connection from pool
    this.client = webdavConnectionPool.getConnection(connectionOptions);
    this.rootPath = config.rootPath;
    
    logger.info('WebDAV service initialized', { 
      rootUrl: config.rootUrl,
      rootPath: config.rootPath,
      authEnabled: authEnabled 
    });
  }

  /**
   * List files and directories at the specified path
   */
  async list(path: string = '/'): Promise<FileStat[]> {
    const fullPath = this.getFullPath(path);
    logger.debug(`Listing directory: ${fullPath}`);
    
    try {
      // In v5.x we need to handle the response differently
      const result = await this.client.getDirectoryContents(fullPath);
      
      // Convert the result to our FileStat interface
      const fileStats = Array.isArray(result) 
        ? result.map(item => this.convertToFileStat(item))
        : this.isResponseData(result) && Array.isArray(result.data)
          ? result.data.map(item => this.convertToFileStat(item))
          : [];
          
      logger.debug(`Listed ${fileStats.length} items in directory: ${fullPath}`);
      return fileStats;
    } catch (error) {
      logger.error(`Error listing directory ${fullPath}:`, error);
      throw new Error(`Failed to list directory: ${(error as Error).message}`);
    }
  }

  /**
   * Get file stats for a specific path
   */
  async stat(path: string): Promise<FileStat> {
    const fullPath = this.getFullPath(path);
    logger.debug(`Getting stats for: ${fullPath}`);
    
    try {
      const result = await this.client.stat(fullPath);
      
      // Convert the result to our FileStat interface
      const stats = this.convertToFileStat(
        this.isResponseData(result) ? result.data : result
      );
      
      logger.debug(`Got stats for: ${fullPath}`, { type: stats.type });
      return stats;
    } catch (error) {
      logger.error(`Error getting stats for ${fullPath}:`, error);
      throw new Error(`Failed to get file stats: ${(error as Error).message}`);
    }
  }

  /**
   * Read file content as text
   */
  async readFile(path: string): Promise<string> {
    const fullPath = this.getFullPath(path);
    logger.debug(`Reading file: ${fullPath}`);
    
    try {
      // v5.x returns buffer by default, need to use format: 'text'
      const content = await this.client.getFileContents(fullPath, { format: 'text' });
      
      // Handle both direct string response and detailed response
      let result: string;
      if (typeof content === 'string') {
        result = content;
      } else if (this.isResponseData(content)) {
        result = String(content.data);
      } else {
        throw new Error("Unexpected response format from server");
      }
      
      const contentLength = result.length;
      logger.debug(`Read file: ${fullPath}`, { contentLength });
      return result;
    } catch (error) {
      logger.error(`Error reading file ${fullPath}:`, error);
      throw new Error(`Failed to read file: ${(error as Error).message}`);
    }
  }

  /**
   * Write content to a file
   */
  async writeFile(path: string, content: string | Buffer): Promise<void> {
    const fullPath = this.getFullPath(path);
    const contentLength = typeof content === 'string' ? content.length : content.length;
    logger.debug(`Writing file: ${fullPath}`, { contentLength });
    
    try {
      // putFileContents in v5.x returns a boolean indicating success
      const result = await this.client.putFileContents(fullPath, content);
      
      // Check result based on type
      if (typeof result === 'boolean' && !result) {
        throw new Error("Failed to write file: server returned failure status");
      } else if (this.isResponseData(result) && 
                 result.status !== undefined && 
                 result.status !== 201 && 
                 result.status !== 204) {
        throw new Error(`Failed to write file: server returned status ${result.status}`);
      }
      
      logger.debug(`Successfully wrote file: ${fullPath}`);
    } catch (error) {
      logger.error(`Error writing to file ${fullPath}:`, error);
      throw new Error(`Failed to write file: ${(error as Error).message}`);
    }
  }

  /**
   * Create a directory
   */
  async createDirectory(path: string): Promise<void> {
    const fullPath = this.getFullPath(path);
    logger.debug(`Creating directory: ${fullPath}`);
    
    try {
      // createDirectory in v5.x returns a boolean indicating success
      const result = await this.client.createDirectory(fullPath);
      
      // Check result based on type
      if (typeof result === 'boolean' && !result) {
        throw new Error("Failed to create directory: server returned failure status");
      } else if (this.isResponseData(result) && 
                 result.status !== undefined && 
                 result.status !== 201 && 
                 result.status !== 204) {
        throw new Error(`Failed to create directory: server returned status ${result.status}`);
      }
      
      logger.debug(`Successfully created directory: ${fullPath}`);
    } catch (error) {
      logger.error(`Error creating directory ${fullPath}:`, error);
      throw new Error(`Failed to create directory: ${(error as Error).message}`);
    }
  }

  /**
   * Delete a file or directory
   */
  async delete(path: string): Promise<void> {
    const fullPath = this.getFullPath(path);
    logger.debug(`Deleting: ${fullPath}`);
    
    try {
      // Get type before deleting for better logging
      const stat = await this.stat(fullPath).catch(() => null);
      const itemType = stat?.type || 'item';
      
      // deleteFile in v5.x returns a boolean indicating success
      const result = await this.client.deleteFile(fullPath);
      
      // Check result based on type
      if (typeof result === 'boolean' && !result) {
        throw new Error("Failed to delete: server returned failure status");
      } else if (this.isResponseData(result) && 
                 result.status !== undefined && 
                 result.status !== 204) {
        throw new Error(`Failed to delete: server returned status ${result.status}`);
      }
      
      logger.debug(`Successfully deleted ${itemType}: ${fullPath}`);
    } catch (error) {
      logger.error(`Error deleting ${fullPath}:`, error);
      throw new Error(`Failed to delete: ${(error as Error).message}`);
    }
  }

  /**
   * Move/rename a file or directory
   */
  async move(fromPath: string, toPath: string): Promise<void> {
    const fullFromPath = this.getFullPath(fromPath);
    const fullToPath = this.getFullPath(toPath);
    logger.debug(`Moving from ${fullFromPath} to ${fullToPath}`);
    
    try {
      // Get type before moving for better logging
      const stat = await this.stat(fromPath).catch(() => null);
      const itemType = stat?.type || 'item';
      
      // moveFile in v5.x returns a boolean indicating success
      const result = await this.client.moveFile(fullFromPath, fullToPath);
      
      // Check result based on type
      if (typeof result === 'boolean' && !result) {
        throw new Error("Failed to move: server returned failure status");
      } else if (this.isResponseData(result) && 
                 result.status !== undefined && 
                 result.status !== 201 && 
                 result.status !== 204) {
        throw new Error(`Failed to move: server returned status ${result.status}`);
      }
      
      logger.debug(`Successfully moved ${itemType} from ${fullFromPath} to ${fullToPath}`);
    } catch (error) {
      logger.error(`Error moving from ${fullFromPath} to ${fullToPath}:`, error);
      throw new Error(`Failed to move: ${(error as Error).message}`);
    }
  }

  /**
   * Copy a file or directory
   */
  async copy(fromPath: string, toPath: string): Promise<void> {
    const fullFromPath = this.getFullPath(fromPath);
    const fullToPath = this.getFullPath(toPath);
    logger.debug(`Copying from ${fullFromPath} to ${fullToPath}`);
    
    try {
      // Get type before copying for better logging
      const stat = await this.stat(fromPath).catch(() => null);
      const itemType = stat?.type || 'item';
      
      // copyFile in v5.x returns a boolean indicating success
      const result = await this.client.copyFile(fullFromPath, fullToPath);
      
      // Check result based on type
      if (typeof result === 'boolean' && !result) {
        throw new Error("Failed to copy: server returned failure status");
      } else if (this.isResponseData(result) && 
                 result.status !== undefined && 
                 result.status !== 201 && 
                 result.status !== 204) {
        throw new Error(`Failed to copy: server returned status ${result.status}`);
      }
      
      logger.debug(`Successfully copied ${itemType} from ${fullFromPath} to ${fullToPath}`);
    } catch (error) {
      logger.error(`Error copying from ${fullFromPath} to ${fullToPath}:`, error);
      throw new Error(`Failed to copy: ${(error as Error).message}`);
    }
  }

  /**
   * Check if a file or directory exists
   */
  async exists(path: string): Promise<boolean> {
    const fullPath = this.getFullPath(path);
    logger.debug(`Checking if exists: ${fullPath}`);
    
    try {
      const result = await this.client.exists(fullPath);
      
      // Handle both boolean and object responses
      let exists = false;
      
      if (typeof result === 'boolean') {
        exists = result;
      } else if (result && typeof result === 'object') {
        // Use type guard for better type safety
        const responseData = result as ResponseData;
        if (responseData.status !== undefined) {
          exists = responseData.status < 400; // If status is less than 400, the resource exists
        }
      }
      
      logger.debug(`Exists check for ${fullPath}: ${exists}`);
      return exists;
    } catch (error) {
      logger.error(`Error checking existence of ${fullPath}:`, error);
      return false;
    }
  }

  /**
   * Type guard to check if an object is a ResponseData
   */
  private isResponseData(value: any): value is ResponseData {
    return value !== null && 
           typeof value === 'object' && 
           'status' in value;
  }

  /**
   * Get the full path by combining root path with the provided path
   */
  private getFullPath(path: string): string {
    // Make sure path starts with / but not with //
    const normalizedPath = path.startsWith('/') ? path : `/${path}`;
    
    // Combine root path with the provided path
    if (this.rootPath === '/') {
      return normalizedPath;
    }
    
    const rootWithoutTrailingSlash = this.rootPath.endsWith('/')
      ? this.rootPath.slice(0, -1)
      : this.rootPath;
      
    return `${rootWithoutTrailingSlash}${normalizedPath}`;
  }

  /**
   * Convert a WebDAV response to our FileStat interface
   */
  private convertToFileStat(item: any): FileStat {
    if (!item) {
      return {
        filename: '',
        basename: '',
        type: 'file'
      };
    }
    
    return {
      filename: item.filename || item.href || '',
      basename: item.basename || this.getBasenameFromPath(item.filename || item.href || ''),
      type: item.type || (item.mime?.includes('directory') ? 'directory' : 'file'),
      size: item.size,
      lastmod: item.lastmod,
      mime: item.mime,
      ...item
    };
  }

  /**
   * Extract basename from a path
   */
  private getBasenameFromPath(path: string): string {
    if (!path) return '';
    const parts = path.split('/').filter(Boolean);
    return parts[parts.length - 1] || '';
  }
}

```