Skip to main content
Glama
README.md
# screen-recorder-mcp

An [MCP](https://modelcontextprotocol.io) server that lets an AI assistant record a video of one app
window, a part of it, or a whole monitor. Works on Windows and macOS.

Window recordings only contain the chosen app. If another window is dragged over it, the recording
keeps showing the app underneath, not the window on top.

```
You:    record the Chrome window for the demo, just the left half
Claude: [calls list_targets, pick_region, start_recording]
        Recording Chrome. Tell me when to stop.
You:    stop
Claude: Saved ~/Videos/screen-recorder-mcp/rec-20261021-153000.mp4 (42 s)
```

![Desktop app](docs/screenshots/main-player.png)

## Features

- Record a single window (by title, app name or id) or a whole monitor.
- Windows in front of the recorded app don't show up in the video.
- Crop: an overlay lets you drag a rectangle over the app to record only that part.
- Optional audio, off by default: system sound or microphone.
- Constant frame rate MP4 (H.264), even when the app isn't redrawing.
- Works without the GPU on Windows: if the GPU can't start a capture session (out of video memory,
  busy with a render or a game), it falls back to CPU capture on its own. You can also force it.
- Optional desktop app laid out like the Windows Snipping Tool: selection overlay, recording toolbar
  with pause, player with trim, GIF export, history and settings. When the app is open, the AI records
  through it, so you always see the toolbar (with an "AI" badge) and can stop it.
- Plain stdio MCP server, so any MCP client can use it (Claude Code, Claude Desktop, Cursor, ...).

## Platform support

| | Windows 10 2004+ / 11 | macOS 13 Ventura+ |
|---|---|---|
| Capture API | Windows Graphics Capture, GDI `PrintWindow` on CPU | ScreenCaptureKit |
| Window recording, ignores apps in front | yes | yes |
| `audio="system"` | everything the PC plays | only the recorded app (all apps for a monitor) |
| `audio="mic"` | default microphone | default microphone |
| Status | tested | **experimental**, not yet tested on real hardware |

Linux isn't supported yet. The OS-specific code sits behind one interface
(`src/screen_recorder_mcp/backends/`), so adding it is mostly a matter of writing a new backend.

## Requirements

- Python 3.11+ and [uv](https://docs.astral.sh/uv/)
- `ffmpeg` on your PATH
  - Windows: `winget install ffmpeg`
  - macOS: `brew install ffmpeg`
- The region picker uses tkinter. uv's managed Python includes it; Homebrew's Python often doesn't.

## Install

```bash
git clone https://github.com/gcamara/screen-recorder-mcp.git
cd screen-recorder-mcp
uv sync
```

### macOS permissions

Give **Screen Recording** permission to the app that launches the server (Terminal, iTerm, the
Claude desktop app, ...) in System Settings > Privacy & Security, then restart that app.
`audio="mic"` also needs **Microphone**.

## Desktop app

```bash
uv run screen-recorder
```

It sits in the tray. **+ New** (or `Ctrl+Shift+R`) opens the selection overlay:

- **Window**: hover and click a window. Only that app is recorded, even if something covers it.
- **Rectangle**: drag an area of the screen. This is a screen region, so whatever is on top of it is recorded too.
- **Full screen**: click a monitor.

Pick a delay (3, 5 or 10 s) in the toolbar if you need time to get ready. While recording:

- a border frames what is being recorded (it follows the window in Window mode);
- the recording bar sits at the top of the screen with pause, stop, discard and the mic / system-audio
  toggles. Drag it anywhere; it remembers the spot. Neither the bar nor the border is ever part of the video.

When you stop, the recording is copied to the clipboard (turn this off in Settings) and opens in the
player: trim the start and end, export a GIF, save, open the folder, or copy it again as video, GIF or
file path (also on right-click in the history). **Recent** in the toolbar, and in the tray menu, lists
the latest recordings; settings are under the gear.

| | |
|---|---|
| ![Selection overlay](docs/screenshots/selection-rectangle.png) | ![Recording toolbar](docs/screenshots/recording-toolbar.png) |
| ![Trim](docs/screenshots/trim-mode.png) | ![Settings](docs/screenshots/settings.png) |

### How the app and the MCP server fit together

```
you ──▶ desktop app (tray, overlay, toolbar, player) ──┐
                                                         ├──▶ recording service (one owner)
AI ───▶ MCP server ──── localhost + token ──────────────┘
         └─ app not running? records on its own, as before
```

The app writes `control.json` (port + random token) to your user's app-data folder and only accepts
local connections that carry the token. The MCP server checks for it on every call: if the app is
running, recordings go through the app (toolbar, history, settings are shared); if not, the MCP server
records by itself. Recordings you make by hand show up in `list_recordings`, so you can tell the AI
"make a GIF of my last recording".

## Connect it to your AI client

Replace `/path/to/screen-recorder-mcp` with where you cloned it.

**Claude Code**

```bash
claude mcp add screen-recorder --scope user -- uv --directory /path/to/screen-recorder-mcp run --no-sync screen-recorder-mcp
```

**Claude Desktop, Cursor and other clients** (`mcpServers` section of the client's config):

```json
{
  "mcpServers": {
    "screen-recorder": {
      "command": "uv",
      "args": ["--directory", "/path/to/screen-recorder-mcp", "run", "--no-sync", "screen-recorder-mcp"]
    }
  }
}
```

Restart the client and the tools below show up.

`--no-sync` matters on Windows: plain `uv run` re-installs the package when dependencies change, and
that fails while another client session has the server's `.exe` open. After pulling a new version,
close your AI clients and run `uv sync` once.

## Usage

Just ask in plain language:

- "Record the Notepad window for 30 seconds."
- "Record my second monitor with the microphone on."
- "Record the YouTube tab." The site is matched against the browser window's title, so that tab has
  to be the active one.
- "Start recording." The assistant lists your windows and asks which one, then asks whether you want
  to crop.

The server tells the assistant to:

1. use the screen, app or site you named, or ask you which one when you didn't;
2. ask whether you want to record only part of it, and if so open the crop overlay;
3. leave audio off unless you asked for it.

**Crop overlay:** drag a rectangle, then press Enter or double-click to confirm. Esc or right-click
records the whole window.

### Tools

| Tool | What it does |
|---|---|
| `list_targets()` | Visible windows (front-most first) and monitors (`monitor:1`, `monitor:2`, ...) |
| `pick_region(target)` | Opens the crop overlay over the target and returns the rectangle you drew (or `null`) |
| `start_recording(target, region?, fps=30, audio="none", capture_cursor=true, output_dir?, capture="auto")` | Starts recording |
| `recording_status()` | Elapsed time, frames written, whether the target window was closed |
| `pause_recording()` / `resume_recording()` | Pause and resume; the paused time is cut out |
| `stop_recording()` | Finishes the file and returns its path and size |
| `cancel_recording()` | Stops and deletes the partial file |
| `list_recordings(limit?)` | Past recordings, newest first (made by you or by the AI) |
| `trim_recording(recording_id, start_seconds, end_seconds)` | Saves a trimmed copy |
| `export_gif(recording_id, high_quality)` | Exports a GIF next to the video |

`target` accepts a window title or part of it, an app name (`chrome`, `notepad.exe`, `Safari`), a
`window_id` from `list_targets`, or `monitor:N`.

`capture` picks how frames are read: `"auto"` (GPU, falling back to CPU), `"gpu"`, or `"cpu"`
(Windows only). Ask for it in plain words, e.g. "record Chrome, use the CPU, my GPU is busy". CPU
capture doesn't draw the mouse cursor and costs more CPU per frame.

Files are saved to `~/Videos/screen-recorder-mcp/` on Windows and `~/Movies/screen-recorder-mcp/` on
macOS, unless you pass `output_dir`.

## Limitations

- A minimized window produces no frames. `start_recording` fails with a message saying so.
- On Windows, `audio="system"` records every app's sound, not only the recorded one.
- Recording a whole monitor captures whatever is on it, including windows on top.
- Some apps block capture of their windows (DRM video, some password managers). Those come out black.
- One recording at a time (shared between the app and the AI).
- The app's global shortcut is Windows-only for now; on macOS open the app from the tray.

## How it works

```
OS capture API ──frames on change──▶ latest-frame slot ──▶ pacer (fixed fps) ──▶ ffmpeg ──▶ MP4
 (WGC / SCK)                                                  crop + size lock
optional audio ──▶ WAV (silence-padded) ─────────────────────────────── mux ──▶ final MP4
```

- **Per-window capture.** Windows Graphics Capture and ScreenCaptureKit both read the window's own
  surface from the compositor, not the pixels on screen. That's why windows on top don't appear.
  The CPU path on Windows asks the window to paint itself into a bitmap (`PrintWindow`), which keeps
  the same guarantee, and stretches windows that aren't DPI-aware the way the desktop shows them.
- **Constant frame rate.** Both APIs only send a frame when the window changes, so a static window
  would produce a 10-second video only a fraction of a second long. A pacer thread re-sends the
  latest frame on a fixed clock.
- **Size lock.** The output size is fixed from the first frame. If the window is resized later,
  frames are padded or trimmed to that size, because ffmpeg's raw input needs a constant size.
- **Audio gaps.** System/app audio sends no packets while nothing is playing. The WAV writer fills
  those gaps with silence so the audio stays in sync with the video.
- **stdout stays clean.** In a stdio MCP server stdout carries the protocol. ffmpeg's output goes to
  a log file, and the crop overlay runs in its own process.

## Development

```bash
uv run pytest -q                                      # unit tests, no desktop needed
uv run python scripts/verify_recording.py notepad     # real recording on this machine
uv run python scripts/verify_recording.py notepad --capture=cpu
```

`verify_recording.py` records a window while a red window covers it, then checks the result with
ffprobe: duration, even dimensions, no red in the frames, and an audio stream when audio is on.
On macOS use `TextEdit` as the window. If you have a Mac, running it there and reporting the output
is the most useful contribution right now.

Layout:

```
src/screen_recorder_mcp/
  server.py            MCP tools
  recorder.py          one recording session (capture → pacer → encoder, audio, mux)
  frame_pacer.py       constant-fps pacing
  region_picker.py     crop overlay (runs as a subprocess)
  frame_sources.py     polling source + GPU→CPU fallback
  backends/windows/    Win32 window list, WGC capture, GDI CPU capture, WASAPI audio
  backends/macos/      Quartz window list, ScreenCaptureKit capture and app audio
```

## License

[MIT](LICENSE)

TDQS

A3.5/5.0

Scored across 11 tools

Disambiguation5/5

Each tool targets a distinct lifecycle step or resource: discovery (list_targets, list_recordings), setup (pick_region), capture control (start/pause/resume/stop/cancel), and post-processing (trim_recording, export_gif). The only near-pair is stop_recording vs cancel_recording, and their descriptions make the keep-vs-delete difference explicit. An agent can select the right tool reliably.

Naming Consistency4/5

Names are uniformly snake_case with clear verb-led phrasing (list_targets, start_recording, pause_recording, trim_recording, export_gif). The lone deviation is recording_status, which is noun-first rather than verb_noun, though it is still readable and unambiguous. Overall predictable.

Tool Count5/5

11 tools is well within the sweet spot and each one maps to a concrete step of the recording workflow. There are no filler or redundant tools; pause/resume/cancel each earn their place as separate operations. The set is nicely scoped to the domain.

Completeness4/5

The surface covers the full capture lifecycle: target discovery, region selection, start, status, pause/resume, stop, cancel, history, trim, and GIF export. Minor gaps exist — there is no tool to delete a saved recording or inspect its duration/metadata, and no still-frame capture — but agents can work around these. Core workflows are fully supported.

Maintenance

ActivityMaintained
ResponsivenessNo issues