# Filesystem Watcher MCP

[![PyPI](https://img.shields.io/pypi/v/filesystemwatcher-mcp.svg)](https://pypi.org/project/filesystemwatcher-mcp)
[![PyPI Downloads](https://img.shields.io/pypi/dm/filesystemwatcher-mcp.svg?label=PyPI%20downloads)](https://pypistats.org/packages/filesystemwatcher-mcp)
[![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-green.svg)](LICENSE)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)

`filesystemwatcher-mcp` lets your AI coding agent (Gemini, Claude, Cursor, Copilot, etc.)
watch directories for live file-system changes. It acts as a
[Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server, giving your
agent event-driven access to file creations, modifications, deletions, and moves —
without busy-polling.

## Key features

- **Event-driven file watching**: Uses [Watchdog](https://github.com/gorakhargosh/watchdog) for cross-platform native OS events (`inotify`, `FSEvents`, `ReadDirectoryChangesW`).
- **Safe by design**: Blocks watching system-critical paths on Windows, macOS, and Linux. Strict path guardrails are enforced server-side.
- **Flexible filtering**: Filter events by file extension, glob pattern, or event type (`created`, `modified`, `deleted`, `moved`).
- **Debounced events**: Rapid successive events on the same file are coalesced over a 500 ms window — no duplicate noise.
- **Consume-once semantics**: `poll_events` drains the queue, making it easy to integrate into agent loops.
- **Explicit error surfacing**: All failure modes (permission denied, OS backend crash, watch limit) return structured `{"success": false, "error": "..."}` responses rather than crashing silently.

## Requirements

- [Python](https://www.python.org/) 3.10 or newer.
- [uv](https://docs.astral.sh/uv/) (recommended) or `pip`.

## Getting started

Add the following config to your MCP client:

```json
{
  "mcpServers": {
    "filesystemwatcher": {
      "command": "uv",
      "args": ["run", "python", "src/server.py"]
    }
  }
}
```

> [!NOTE]
> Make sure you have cloned the repository and installed dependencies (`uv sync`) before pointing your MCP client at the server.

### MCP Client configuration

<details>
  <summary>Antigravity</summary>

To use the Filesystem Watcher MCP server, follow the instructions from <a href="https://antigravity.google/docs/mcp">Antigravity's docs</a> to install a custom MCP server. Add the following config to the MCP servers config:

```json
{
  "mcpServers": {
    "filesystemwatcher": {
      "command": "uv",
      "args": ["run", "python", "src/server.py"]
    }
  }
}
```

</details>

<details>
  <summary>Claude Code</summary>

Use the Claude Code CLI to add the server (<a href="https://code.claude.com/docs/en/mcp">guide</a>):

```bash
claude mcp add filesystemwatcher --scope user uv run python src/server.py
```

</details>

<details>
  <summary>Copilot / VS Code</summary>

Follow the MCP install <a href="https://code.visualstudio.com/docs/copilot/chat/mcp-servers#_add-an-mcp-server">guide</a> and use the standard config from above.

</details>

<details>
  <summary>Cursor</summary>

Go to `Cursor Settings` -> `MCP` -> `New MCP Server`. Use the config provided above.

</details>

<details>
  <summary>Gemini CLI</summary>

```bash
gemini mcp add filesystemwatcher uv run python src/server.py
```

Alternatively, follow the <a href="https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md#how-to-set-up-your-mcp-server">MCP guide</a> and use the standard config from above.

</details>

<details>
  <summary>Windsurf</summary>
  Follow the <a href="https://docs.windsurf.com/windsurf/cascade/mcp#mcp-config-json">configure MCP guide</a> using the standard config from above.
</details>

### Your first prompt

Enter the following prompt in your MCP client to check if everything is working:

```
Watch my home directory for any new file creations and tell me when something appears.
```

Your agent should call `watch_directory` and then periodically call `poll_events` to report changes.

## Tools

- **File system watching** (4 tools)
  - [`watch_directory`](#watch_directory)
  - [`poll_events`](#poll_events)
  - [`list_active_watches`](#list_active_watches)
  - [`unwatch`](#unwatch)

### `watch_directory`

Start watching a directory for file system events. Returns a `watch_id` used to identify this watch session.

| Argument | Type | Default | Description |
|---|---|---|---|
| `path` | `str` | **required** | Absolute path to the directory to watch |
| `recursive` | `bool` | `false` | Also watch all subdirectories |
| `extensions` | `list[str]` | `null` | Filter by extension, e.g. `[".py", ".js"]` |
| `pattern` | `str` | `null` | Glob pattern matched against file name, e.g. `"*.log"` |
| `ignore_patterns` | `list[str]` | `null` | Glob patterns to exclude, e.g. `["*.tmp", ".git/*"]` |
| `event_types` | `list[str]` | `null` | Subset of `["created", "modified", "deleted", "moved"]` |

**Error responses** — on failure `success` is `false` and `error` explains why:

| Condition | Example error |
|---|---|
| Path blocked by safety guardrails | `"Watching '/etc' is not allowed: protected system location."` |
| Process lacks read permission | `"Permission denied: cannot watch '/protected'. Check read access."` |
| OS watcher backend failed to start | `"Filesystem observer failed to start for '...'. Backend may be unavailable."` |
| Concurrent watch limit reached | `"Watch limit reached (50 active watches). Call unwatch() first."` |

### `poll_events`

Drain and return queued file system events (consume-once semantics).

| Argument | Type | Default | Description |
|---|---|---|---|
| `watch_id` | `str` | `null` | Filter events by watch ID; if omitted, all watches are drained |
| `max_events` | `int` | `50` | Maximum events to return (1–500) |

If `watch_id` is provided but does not match any active watch, the response includes a `warning` field to prevent agents from silently spinning on a stale ID:

```json
{
  "count": 0,
  "events": [],
  "warning": "No active watch found with id 'abc-123'. Use list_active_watches to see current watch IDs."
}
```

### `list_active_watches`

Return a summary of all currently active watches.

### `unwatch`

Stop and remove a watch by `watch_id`.

## Configuration

The server currently has no command-line flags; all configuration is done via the
tool arguments at runtime.

You can inspect available tools interactively with the MCP Inspector:

```bash
npx @modelcontextprotocol/inspector mcp dev src/server.py
```

## Safety guardrails

The server blocks watching dangerous system paths:

| Platform | Blocked (exact) | Blocked (subtree) |
|---|---|---|
| Windows | `C:\` | `C:\Windows`, `C:\Program Files`, `C:\ProgramData` |
| Linux | `/` | `/etc`, `/sys`, `/proc`, `/usr`, `/dev`, `/boot`, … |
| macOS | `/` | `/System`, `/Library`, `/usr`, `/private`, … |

Watching `C:\Users` or `/home` directly is also blocked; individual user home directories are allowed.

**Concurrent watch limit**: A maximum of **50 active watches** are allowed at any time to prevent exhausting the OS inotify quota. Call `unwatch` to free a slot before creating a new one.

> [!WARNING]
> The MCP server exposes file-system event data to MCP clients. Avoid watching directories that contain sensitive or personal data you would not want shared with your AI agent.

## Debouncing

Events are coalesced over a **500 ms window** per `(event_type, path)` key. Rapid
successive events on the same file (e.g. multiple writes during a save) are
reported as a single event.

## Development

```bash
# Clone and install dependencies
git clone https://github.com/your-org/filesystemwatcher-mcp
cd filesystemwatcher-mcp
uv sync

# Run the server directly
uv run python src/server.py

# Run with MCP Inspector
npx @modelcontextprotocol/inspector mcp dev src/server.py

# Run tests
uv run pytest

# Security audit
uv run pip-audit
```

## Known limitations

- Only local file systems are supported; network mounts may not deliver native OS events reliably.
- The server must be restarted to pick up changes to ignored/blocked path configuration.
