Skip to main content
Glama
NasirSultan

desktop-control-mcp

by NasirSultan
README.md
# desktop-control-mcp

An MCP server that lets an MCP client (Claude Code, Claude Desktop, or claude.ai via a
custom connector) open and close applications, files, and folders on this Windows machine.

Tools exposed:

| Tool | Does |
|---|---|
| `open_app` | Launch an app by friendly name (`chrome`, `word`, `notepad`, ...) or `.exe` name, optionally with an argument (URL/file) |
| `close_app` | Force-close a running app by name |
| `list_running_apps` | List processes that currently have a visible window |
| `open_path` | Open a file/folder path, or a friendly folder name (`documents`, `desktop`, `downloads`, `pictures`, `music`, `videos`, `home`) |
| `close_folder_window` | Close any open File Explorer window(s) showing a given folder |
| `list_folder_contents` | List the files/subfolders directly inside a folder (name, type, path, size) |
| `open_path_with` | Open a file/folder with a SPECIFIC app (e.g. a folder in VS Code) - `app` is required |
| `find_duplicate_files` | Recursively find duplicate files by content (name, all locations, count, wasted space) |
| `list_open_folders` | List every open File Explorer folder window (path, title, hwnd) |
| `close_all_folder_windows` | Close every open File Explorer folder window |
| `focus_folder_window` | Bring an open folder window to front (un-minimizes if needed) |
| `minimize_folder_window` | Minimize an open folder window |
| `list_chrome_tabs` | List every open Chrome tab (index + title) |
| `open_chrome_tab` | Open a new Chrome tab, optionally to a URL |
| `close_chrome_tab` | Close the open tab matching a title search |
| `focus_chrome_tab` | Switch to the open tab matching a title search |
| `scroll_chrome_page` | Maximize Chrome and scroll the active page up/down (simulated PageUp/PageDown) |
| `list_chrome_profiles` | List configured Chrome profiles (name + directory) |
| `open_chrome_profile` | Open a new Chrome window under a specific profile (most reliable when Chrome isn't already running) |
| `focus_app` | Bring an app's window to front / switch to it (un-minimizes if needed) |
| `maximize_app` | Maximize an app's window |
| `minimize_app` | Minimize an app's window |
| `system_info` | Report OS, CPU, RAM (total/used/free/%), and disk usage per drive |
| `restart_graphics_driver` | Send Ctrl+Shift+Win+B to restart the graphics driver - fixes a frozen/stuck/black/glitched screen without closing apps or losing work |
| `shutdown_pc` | Schedule a full shutdown after a delay. Requires `confirm: true`. |
| `restart_pc` | Schedule a restart after a delay. Requires `confirm: true`. |
| `cancel_shutdown` | Abort a pending `shutdown_pc`/`restart_pc` before it fires |

Known app aliases live in [src/appMap.js](src/appMap.js) — add more there as needed. Any
other name is tried as a literal `.exe`.

`open_path_with`'s `app` field is required on purpose: if a request doesn't say which app
to use, the calling model should ask you rather than guess — making the field required
enforces that at the schema level instead of relying on prompt wording. Use plain
`open_path` for "just open it with whatever's default" requests.

`focus_app` / `maximize_app` / `minimize_app` take an optional `pid`. If more than one
window of the requested app is open, they refuse to guess — the error lists each
candidate's PID and window title so you (or Claude) can retry with the right `pid`.

`find_duplicate_files` matches by content (SHA-256), not filename — two files named
differently but byte-identical count as duplicates; two files with the same name but
different content don't. It groups by file size first and only hashes within a group
that has more than one file, so a folder with mostly unique-sized files is fast. It stops
after 20000 files on a very large tree and sets `truncated: true` rather than hanging.

### Chrome tabs

`close_chrome_tab` / `focus_chrome_tab` match by a case-insensitive substring against tab
titles (not URLs) and take an optional `index` the same way the window tools take `pid` -
if the query matches more than one tab, they list the candidates instead of guessing.
Built on Chrome's own UI Automation tree (the same mechanism screen readers use), not
`taskkill`/Win32 windows, since individual tabs aren't OS-level windows at all.

Two things worth knowing:
- Only the **first** Chrome window is used if you have more than one open — tabs in a
  second Chrome window aren't visible to these tools.
- The **first** Chrome tab call after Chrome starts (or after it's been idle a while) can
  return an empty list while Chrome's accessibility tree activates. If you get `[]` from
  `list_chrome_tabs` when you know tabs are open, just call it again.

## ⚠️ Security model — read before exposing this to the internet

This server can launch and kill processes on your laptop. **Anyone who can send it a
request has meaningful control of your machine.** It is not a toy:

- The HTTP transport (`src/http.js`) **refuses to start without `MCP_AUTH_TOKEN` set**
  and requires `Authorization: Bearer <token>` on every request. There is no bypass for
  this — don't add one.
- It binds to `127.0.0.1` only, never `0.0.0.0`. The only way it becomes reachable from
  outside this machine is if you deliberately point ngrok (or something else) at it.
- Every request is logged to stdout with timestamp and source IP, including rejected
  (unauthenticated) ones — keep an eye on that console while a tunnel is open.
- A small denylist (`PROCESS_DENYLIST` in [src/appMap.js](src/appMap.js)) blocks closing
  core OS processes (`lsass.exe`, `winlogon.exe`, `services.exe`, ...) so a bad request
  can't easily crash the whole session.
- All dynamic input (app names, paths) is passed to child processes via environment
  variables, never string-concatenated into a shell/PowerShell command line — this is
  what prevents a crafted tool argument from smuggling in extra commands.

### `shutdown_pc` / `restart_pc` — the two tools that can strand you

These are the most destructive tools in this server, so they have three independent
safety nets:

1. **`confirm: true` is required.** Omit it and the tool refuses before touching
   `shutdown.exe` — a guard against an accidental or ambiguous call.
2. **A countdown delay** (default 60s, min 15s, max 3600s) before anything happens.
3. **`cancel_shutdown`** aborts a pending shutdown/restart during that countdown.

Two things worth knowing that aren't obvious from the tool names:

- **Unsaved work is not protected.** Windows' `shutdown.exe` automatically implies
  force-close (`/f`) whenever the delay is greater than 0 seconds — every default call
  here has a delay, so once the countdown reaches zero, open apps are closed without a
  save prompt. The delay is your only window to react, not a "Windows will ask first"
  safety net.
- **`restart_pc` currently ends remote control, same as `shutdown_pc`.** This server
  doesn't auto-start on boot, so after either one fires you'll need to be physically at
  the machine to get remote control back (auto-start-on-login is a possible follow-up,
  not set up here).

If you ever need to cancel by hand instead of through an MCP tool call, run `shutdown /a`
from a normal `cmd.exe` or PowerShell window — **not Git Bash**. Git Bash's path
auto-conversion rewrites leading-slash flags like `/a` into a bogus Windows path before
`shutdown.exe` ever sees it, so the command silently does nothing (prints usage/help,
exit code 1) instead of cancelling. This bit us during testing of this feature.

What this does **not** do: there is no arbitrary shell-command tool here on purpose.
"Open/close apps and files" is a large enough attack surface already; a generic
`run_command` tool would turn this into an unrestricted remote shell. If you extend this
server, keep that boundary in mind.

Practical rules for running this safely:

1. **Treat `MCP_AUTH_TOKEN` like a root password.** Generate a long random one (see
   `.env.example`), never commit it, never post the ngrok URL anywhere public.
2. **Only run the tunnel while you're actively using it.** Stop `ngrok` (and the server,
   if you want to be extra safe) when you're done for the day.
3. **Prefer ngrok's own auth on top of yours.** `ngrok http 3939 --oauth=google
   --oauth-allow-email=you@example.com` (requires a paid ngrok plan) or at least
   `ngrok http 3939 --basic-auth "user:longpassword"` adds a second lock on the door.
4. **Watch the console.** Every open/close call this server executes is printed. If you
   see activity you didn't trigger, kill the ngrok tunnel immediately (Ctrl+C) and
   rotate `MCP_AUTH_TOKEN`.
5. A free ngrok URL changes every time you restart the tunnel — that's a feature, not a
   bug, since a stale forgotten URL still needs your (rotatable) token to do anything.
6. **Try `shutdown_pc`/`restart_pc` locally over stdio first**, with a generous
   `delaySeconds` and `cancel_shutdown` ready to go, before ever calling them through the
   ngrok tunnel where a dropped connection could keep you from cancelling in time.

## Setup

```bash
npm install
copy .env.example .env
```

Edit `.env` and set `MCP_AUTH_TOKEN` to a long random value:

```bash
node -e "console.log(require('crypto').randomBytes(24).toString('hex'))"
```

## Running locally (stdio) — for Claude Code / Claude Desktop on this machine

No token needed for stdio; the client launches the process directly, so there's no
network exposure.

Claude Code:

```bash
claude mcp add desktop-control -- node "C:\Users\Al-Fateh\Documents\agentic-system\desktop-control-mcp\src\stdio.js"
```

Claude Desktop (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "desktop-control": {
      "command": "node",
      "args": ["C:\\Users\\Al-Fateh\\Documents\\agentic-system\\desktop-control-mcp\\src\\stdio.js"]
    }
  }
}
```

## Running remotely (HTTP + ngrok) — for claude.ai custom connectors

1. Start the server:

   ```bash
   npm run start:http
   ```

   It listens on `http://127.0.0.1:3939/mcp` and will refuse to start if
   `MCP_AUTH_TOKEN` isn't set.

2. In a separate terminal, tunnel it:

   ```bash
   ngrok http 3939
   ```

   Note the `https://<random>.ngrok-free.app` URL ngrok prints.

3. Connect a client to `https://<random>.ngrok-free.app/mcp` with header
   `Authorization: Bearer <your MCP_AUTH_TOKEN>`:

   - **Claude Code**: `claude mcp add --transport http desktop-control https://<random>.ngrok-free.app/mcp --header "Authorization: Bearer <token>"`
   - **claude.ai** (Settings → Connectors → Add custom connector): paste the URL. If the
     UI doesn't offer a header field for your account type, you'll need OAuth-based auth
     instead of a static bearer token to use claude.ai's hosted connector flow — the
     bearer-token approach above is guaranteed to work with Claude Code today.

4. When you're done, `Ctrl+C` both the ngrok tunnel and the server.

## Files

```
src/appMap.js     friendly-name -> exe/process map, and the process denylist
src/winControl.js the actual open/close logic (PowerShell + taskkill, injection-safe)
src/server.js     MCP tool definitions, shared by both transports
src/stdio.js      entry point for local stdio transport
src/http.js       entry point for remote HTTP transport (auth required)
```

TDQS

A3.8/5.0

Scored across 27 tools

Disambiguation5/5

Each tool targets a distinct resource-action pair: app launching/windowing, folder paths/windows, Chrome profiles/tabs, and system control are clearly separated. Even the three 'open' tools differ by target type (app name, default handler, specific app), and close/focus/minimize variants are unambiguous.

Naming Consistency5/5

Nearly all tools follow a verb_noun snake_case pattern (list_*, open_*, close_*, focus_*, minimize_*), with only system_info and open_path_with as minor deviations. The naming is highly predictable and makes the tool set easy to navigate.

Tool Count3/5

At 27 tools, this is on the heavy side, but the broad desktop-control scope justifies the count: app management, folder windows, Chrome control, and system power actions are all represented. A few niche tools (scroll_chrome_page, restart_graphics_driver) could be considered optional, making it feel slightly bloated.

Completeness4/5

The tool set covers the major desktop workflows: app launch/focus/window-state, folder browsing and window management, Chrome tab/profile control, and system shutdown/restart. Minor gaps exist—no maximize folder window, no way to pass command-line args to open_app—but nothing blocks the core use cases.

Maintenance

ActivityMaintained
ResponsivenessNo issues