remote-control-mcp
by Hexpy-Games
README.md
# remote-control-mcp
[](./LICENSE)
[](https://nodejs.org)
[](https://github.com/modelcontextprotocol/typescript-sdk)
An MCP server that gives AI services (Claude, Cursor, etc.) remote control of your Mac — execute shell commands, read/write files, and run AppleScript — secured with OAuth 2.0 + PKCE.
## Demo

> Claude searches for a tteok shop on Naver Maps, gets driving directions, takes a screenshot, and emails the result. All done in the Claude app on iOS.
---
## What it does
- Exposes your Mac as an MCP server reachable over the internet through a secure tunnel
- Runs shell commands (`zsh`) asynchronously and returns stdout, stderr, and exit code
- Reads and writes files and directories using `fs.promises` — no shell injection surface
- Executes AppleScript for UI automation and app control
- Protects every tool call with OAuth 2.0 + PKCE; token rotation revokes old tokens immediately
- Optional file server (port 3835) to share files from `~/Public/mcp-files/` via browser or any HTTP client
---
## ⚠️ Security Warning
This server provides **remote shell execution** on your Mac.
- **OAuth 2.0 + PKCE** protects the MCP endpoint — valid tokens are required for all tool calls
- **PIN-gated authorization** — connecting a new client requires a one-time PIN generated by `rcmcp auth` on your local machine. The PIN is never written to disk and is only accessible via localhost. Anyone who reaches `/authorize` without the PIN cannot obtain a token.
- **The network layer is your second line of defense** — always terminate TLS before exposing the server. Plain HTTP exposes OAuth tokens in transit regardless of whether you use a domain name or a raw IP.
**This server is designed for personal, single-user use behind a private tunnel. Do not expose it to untrusted networks.**
---
## Requirements
- **macOS** (Linux support planned; AppleScript tools are macOS-only)
- **Node.js** `>=20.16.0` or `>=22.3.0`
- A tunnel to expose the server — Cloudflare Tunnel, ngrok, or Tailscale (see below)
- **Redis** (required in production for token persistence; in-memory fallback used without it)
---
## Quick Start
### Automated (recommended)
`setup.sh` handles everything interactively — dependencies, `.env`, build, tunnel, LaunchAgent, and CLI:
```bash
git clone https://github.com/hexpy-games/remote-control-mcp
cd remote-control-mcp
./setup.sh
```
### Manual
```bash
git clone https://github.com/hexpy-games/remote-control-mcp
cd remote-control-mcp
npm install
cp .env.example .env
# Edit .env — set BASE_URI to your tunnel URL
npm run build
npm start
```
Then set up a tunnel (see below) and add the server URL to your AI client.
---
## Tunnel Options (for manual setup)
Never expose the server over plain HTTP. Always terminate TLS — via a tunnel or a reverse proxy:
> **`setup.sh` automates Options 1 and 2** (Cloudflare Tunnel and ngrok).
### Option 1: Cloudflare Tunnel (recommended — free, stable URL)
```bash
brew install cloudflared
# Permanent subdomain (requires free Cloudflare account)
cloudflared tunnel login
cloudflared tunnel create remote-control-mcp
cloudflared tunnel route dns remote-control-mcp your-subdomain.yourdomain.com
# Set BASE_URI=https://your-subdomain.yourdomain.com in .env
cloudflared tunnel run remote-control-mcp
```
One-shot (URL changes each run, useful for testing):
```bash
cloudflared tunnel --url http://localhost:3232
```
### Option 2: ngrok (quick setup, generous free tier)
```bash
brew install ngrok
ngrok config add-authtoken <your-token>
ngrok http 3232
# Copy the https URL and set it as BASE_URI in .env
```
> The ngrok free tier assigns a random URL on each restart — update `BASE_URI` in `.env` after each restart, then run `rcmcp restart server`. Paid plans support a fixed static domain.
### Option 3: Self-hosted (advanced)
Only use this if you have proper TLS termination in place:
1. Forward port 3232 on your router to your Mac
2. Set `BASE_URI` to your domain or public IP in `.env`
3. Terminate TLS at a reverse proxy — plain HTTP exposes OAuth tokens in transit
```bash
# Example: Caddy as a TLS-terminating reverse proxy
brew install caddy
# Caddyfile:
# your-domain.com {
# reverse_proxy localhost:3232
# }
caddy run
```
---
## Connect to Claude
After the server is running and a tunnel is up:
### 1. Add the MCP server in Claude
In **Claude.ai** (desktop or web): go to **Settings → Integrations → Add Integration** and paste your server URL:
```
https://your-tunnel-url/mcp
```
Claude will immediately start an OAuth authorization flow.
### 2. Authorization
A browser window will open asking you to approve the connection. This is where you enter the **Server PIN**.
**Get your PIN:**
```bash
rcmcp auth
```
This generates a fresh one-time PIN, copies it to your clipboard, and waits for you to complete the authorization:
```
Authorization PIN
────────────────────────────────────────
9aRs-VhzM (copied to clipboard)
→ Enter this PIN on the /authorize approval page.
⠙ Waiting for authorization...
```
Paste the PIN into the browser form and click **Approve**. The terminal will confirm automatically:
```
✓ Authorization complete
```
### 3. Start using remote tools
Once authorized, Claude has access to four tools on your Mac:
| Tool | What it does |
|---|---|
| `shell_exec` | Run any zsh command |
| `osascript` | Run AppleScript (UI automation, app control) |
| `file_read` | Read files or list directories |
| `file_write` | Write files (creates directories as needed) |
**Example prompts to try:**
- *"Take a screenshot and describe what's on my screen"*
- *"What files are in my Downloads folder?"*
- *"Search for coffee shops near me on Maps and send me the directions"*
> **Other clients:** Any MCP-compatible client (Cursor, Windsurf, etc.) can connect using the same server URL. The OAuth flow is standard.
---
## Configuration
Copy `.env.example` to `.env` and edit as needed.
| Variable | Default | Description |
|---|---|---|
| `PORT` | `3232` | Port the MCP server listens on |
| `BASE_URI` | `http://localhost:3232` | Public URL of this server — must match your tunnel URL |
| `NODE_ENV` | `development` | Set to `production` when deploying |
| `BLOCKED_COMMANDS` | _(empty)_ | Comma-separated substrings to block in `shell_exec` |
| `REDIS_URL` | _(unset)_ | Redis connection URL — required in production |
| `REDIS_TLS` | `0` | Set to `1` to enable TLS for the Redis connection |
### Redis
Redis is **required in production** for token persistence and expiry. Without it, tokens are stored in memory and lost on restart.
```bash
# Docker
docker run -d -p 6379:6379 redis:7-alpine
# Homebrew
brew install redis && brew services start redis
```
Set `REDIS_URL=redis://localhost:6379` in `.env`.
---
## Available Tools
| Tool | Description |
|---|---|
| `shell_exec` | Execute a `zsh` command on the Mac. Runs asynchronously; supports concurrent commands. Returns stdout, stderr, and exit code. |
| `osascript` | Execute an AppleScript. Scripts are written to a private temp directory to prevent TOCTOU races. Use for UI automation and app control. |
| `file_read` | Read file contents or list a directory. Uses `fs.promises` directly — no shell spawning. |
| `file_write` | Write content to a file. Creates intermediate directories if needed. |
### Security notes
- `BLOCKED_COMMANDS` is a last-resort safeguard, **not** a security boundary. Security is enforced at the OAuth + tunnel layer.
- Refresh token rotation revokes the previous token immediately.
- A default blocklist prevents the most catastrophic commands (`rm -rf /`, fork bombs, direct disk writes, etc.).
---
## rcmcp CLI
`setup.sh` installs `rcmcp`, a management CLI for day-to-day operations:
```bash
rcmcp status # server + tunnel + file server status and endpoint URL
rcmcp start [server|tunnel|fileserver|all]
rcmcp stop [server|tunnel|fileserver|all]
rcmcp restart [server|tunnel|fileserver|all]
rcmcp logs [server|tunnel|fileserver|all] [-f]
rcmcp url # print current MCP endpoint URL
rcmcp update # git pull → rebuild → restart server
rcmcp uninstall # remove LaunchAgents, binary, and PATH entry
```
Targets can be abbreviated: `srv`, `tun`, `fs`.
---
## File Server (optional)
`setup.sh` can install an optional lightweight file server (port 3835) that serves files from `~/Public/mcp-files/` over HTTP. Useful for sharing or viewing Mac files from any browser, HTTP client, or AI service.
```
GET https://your-tunnel-url/files/<filename>
```
Managed via `rcmcp start fileserver` / `rcmcp stop fileserver`.
---
## Development
```bash
# Run with live reload
npm run dev
# Type-check without emitting
npm run typecheck
# Lint
npm run lint
# Build
npm run build
```
---
## Contributing
### Reporting issues
- **Bug**: Open an issue with the prefix `[bug]`. Include OS version, Node version, reproduction steps, and expected vs. actual behavior.
- **Feature request**: Open an issue with the prefix `[feat]`. Describe the use case, not just the solution.
### Submitting a PR
1. Fork the repo and create a branch from `main`
2. Follow branch naming conventions:
- `feat/` — new feature
- `fix/` — bug fix
- `docs/` — documentation only
- `chore/` — tooling, deps, CI
3. Run `npm run lint` and `npm run typecheck` — both must pass
4. Open a PR against `main` with a clear description of what and why
### Commit style
Use [Conventional Commits](https://www.conventionalcommits.org/):
```
feat: add Tailscale tunnel option
fix: prevent shell_exec hanging on commands with no output
docs: clarify Redis requirement in README
chore: upgrade MCP SDK to 1.25
```
### What gets accepted
- Security improvements
- New MCP tools that fit the "remote Mac control" scope
- Additional tunnel provider support
- Bug fixes
### What gets rejected
- Changes that remove the OAuth requirement
- Features that broaden scope to multi-user or server-side use cases
---
## License
MIT — Copyright (c) 2026 Hexpy Games & remote-control-mcp contributors
See [LICENSE](./LICENSE) for the full text.
---
## Disclaimer
This project is not affiliated with, endorsed by, or sponsored by Anthropic, Claude.ai, or any other AI service provider.
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues