remote-control-mcp
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 authon your local machine. The PIN is never written to disk and is only accessible via localhost. Anyone who reaches/authorizewithout 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.0or>=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:
git clone https://github.com/hexpy-games/remote-control-mcp
cd remote-control-mcp
./setup.sh
Manual
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.shautomates Options 1 and 2 (Cloudflare Tunnel and ngrok).
Option 1: Cloudflare Tunnel (recommended — free, stable URL)
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):
cloudflared tunnel --url http://localhost:3232
Option 2: ngrok (quick setup, generous free tier)
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_URIin.envafter each restart, then runrcmcp 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:
- Forward port 3232 on your router to your Mac
- Set
BASE_URIto your domain or public IP in.env - Terminate TLS at a reverse proxy — plain HTTP exposes OAuth tokens in transit
# 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:
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.
# 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_COMMANDSis 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:
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
# 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
- Fork the repo and create a branch from
main - Follow branch naming conventions:
feat/— new featurefix/— bug fixdocs/— documentation onlychore/— tooling, deps, CI
- Run
npm run lintandnpm run typecheck— both must pass - Open a PR against
mainwith a clear description of what and why
Commit style
Use Conventional Commits:
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 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.