mcp server
remote-claws
Allow your agent/openclaw to do things remotely, with controls, on a Desktop machine via MCP
Set up
Runs on your computer. Pick your agent and operating system.
Before you start
- uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
{
"mcpServers": {
"remote-claws": {
"command": "uvx",
"args": [
"remote-claws"
]
}
}
}same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12
Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs
README
synced Oct 25, 2026Remote Claws
An MCP server that gives AI agents full control of a remote desktop machine — browser automation, mouse/keyboard, command execution, and file transfer. Deploy it on a PC, connect your agent, and let it work.
What It Does
Remote Claws exposes 4 tools (39 actions) over MCP via HTTP. Each tool takes an action parameter, like a CLI subcommand:
| Tool | Actions | What It Controls |
|---|---|---|
| remote_browser | 16 | Navigate, click, fill forms, read text, run JS, take screenshots — real Chrome with your identity |
| remote_desktop | 12 | Mouse clicks, keyboard input, screenshots, window management — pyautogui (cross-platform) + pywinauto (Windows-only element inspection) |
| remote_exec | 5 | Start processes, stream stdout/stderr, send stdin, kill — fully async |
| remote_files | 6 | Read, write, list, move, delete — base64 transfer with chunked reads |
The remote_ prefix is deliberate: agents often have their own local browser/exec/read/write tools, and identical names cause the model to confuse which machine it's acting on.
See TOOLS.md for the complete tool reference and SKILLS.md for a high-level capability overview.
Quick Start
git clone https://github.com/wentbackward/remote-claws.git
cd remote-claws
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # Linux/macOS
pip install -e .
No playwright install step is needed — the default browser_channel=chrome drives your system Chrome, not a bundled browser.
1. Generate Auth Token
remote-claws-setup
This prints a bearer token once — copy it now. Only the SHA-256 hash is stored on disk. The setup script also:
- Prompts for transport choice (SSE or Streamable HTTP)
- Offers to chain into
remote-claws-browser-setup
2. Seed the Browser Profile (Optional but Recommended)
remote-claws-browser-setup
# or jump straight to a site to log into:
remote-claws-browser-setup --url https://nytimes.com
Chrome opens on a dedicated profile (separate from your daily Chrome) under %LOCALAPPDATA%\RemoteClaws\chrome-profile on Windows, ~/.local/share/remote-claws/chrome-profile on Linux, ~/Library/Application Support/RemoteClaws/chrome-profile on macOS. Sign into the services you want the agent to access, install your adblocker, accept cookie banners, then close the window. Sessions persist across server restarts.
The browser group drives system-installed Google Chrome with this persistent profile — this is the whole point of the feature: the agent browses like you do, signed into subscriptions, past bot walls, with your adblocker.
You probably do not want the bundled Playwright Chromium (browser_channel=chromium). If all you need is a plain automatable browser, run one locally where your agent lives (e.g. openclaw's built-in browser) — don't stand up a remote server for that. The chromium channel exists only for CI and similar throwaway environments, and requires playwright install chromium.
The server hard-fails at startup if browser_channel=chrome (the default) and Chrome isn't installed.
3. Start the Server
remote-claws
The server runs in the foreground on 0.0.0.0:8080 by default. Agents connect to http://<your-ip>:8080/mcp (Streamable HTTP, the default) or http://<your-ip>:8080/sse (legacy SSE) with the bearer token.
Security
Authentication
Every connection requires a bearer token in the Authorization header. The server:
- Stores only a SHA-256 hash of the token (never the raw token)
- Uses timing-safe comparison (
hmac.compare_digest) to prevent timing attacks - Refuses to start if no auth file exists
- Returns 401 for missing, malformed, or invalid tokens
To rotate the token, run remote-claws-setup again.
Permission Policy
permissions.json controls what the agent can do. Each tool group (browser, desktop, exec, files) has allow and deny lists of bare action names. Deny always supersedes allow.
{
"permissions": {
"browser": { "allow": ["*"], "deny": [] },
"desktop": { "allow": ["*"], "deny": ["click_element"] },
"exec": { "allow": ["run", "get_output", "list"], "deny": [] },
"files": { "allow": ["read", "list", "info"], "deny": [] }
}
}
Use "*" to allow/deny an entire group. Omitting a group denies it entirely.
Enforcement is two-tier:
- Group level (registration time): a disabled or fully-denied group means its tool is never registered — it doesn't show up in
tools/listat all. - Action level (call time): within a registered tool, a denied action returns
{"error": "permission denied: <group>:<action>"}. The tool can't partially hide its own description, so the agent learns the boundary from the error.
Legacy entries using pre-consolidation tool names (browser_navigate, file_read) are auto-normalized to bare action names at load, with a deprecation warning in the server log.
Disabling Whole Tool Groups at Startup
Set enabled_groups to skip a group entirely. Disabled groups are never imported, so heavy dependencies (Playwright for browser, pyautogui for desktop) stay out of memory:
REMOTE_CLAWS_ENABLED_GROUPS="exec,files" remote-claws
Or in remote-claws.json:
{ "enabled_groups": "exec,files" }
A group is active only when it appears in enabled_groups and its permissions.json entry permits at least one tool.
IP Allowlist
Lock the server to specific source IPs. Connections from any other IP are rejected with 403 before auth is even checked:
REMOTE_CLAWS_ALLOWED_IPS="100.82.48.9,100.106.2.100" remote-claws
Or in remote-claws.json:
{ "allowed_ips": "100.82.48.9,100.106.2.100" }
When empty (the default), IP filtering is disabled and access relies on bearer token auth alone.
Defense in Depth
The server has three independent security layers, outermost first:
- IP allowlist — rejects connections from unknown IPs (403, before any processing)
- Bearer token — rejects unauthenticated requests (401, timing-safe hash comparison)
- Permission policy — restricts which tools are available (per-tool granularity)
For production, also consider:
- Run behind a VPN (Tailscale, WireGuard) for encrypted transport
- Use firewall rules as an additional network-level gate
- Put behind a reverse proxy with TLS for encryption in transit
Connecting Agents
Remote Claws supports two transport protocols, one at a time (chosen at setup or via REMOTE_CLAWS_TRANSPORT):
| Transport | Path | Clients |
|---|---|---|
| Streamable HTTP (current MCP spec, default) | /mcp | openclaw, Claude Code, newer SDKs |
| SSE (legacy, deprecated by the MCP spec) | /sse | Claude Desktop and older clients |
remote-claws-setup prompts for the choice and writes it to remote-claws.json. To serve SSE clients, set "transport": "sse". Both transports share the same browser tabs and process tracker — reconnecting clients reattach the same session.
Claude Desktop
Claude Desktop uses the legacy SSE transport, so set "transport": "sse" in remote-claws.json first. Then add to your Claude Desktop config (%APPDATA%\Claude\claude_desktop_config.json on Windows):
{
"mcpServers": {
"remote-claws": {
"command": "npx",
"args": [
"mcp-remote",
"http://YOUR_IP:8080/sse",
"--header",
"Authorization:${REMOTE_CLAWS_TOKEN}"
],
"env": {
"REMOTE_CLAWS_TOKEN": "Bearer YOUR_TOKEN_HERE"
}
}
}
}
Claude Code
claude mcp add remote-claws \
--transport http \
--url http://YOUR_IP:8080/mcp \
--header "Authorization: Bearer YOUR_TOKEN_HERE"
LangChain / LangGraph
from langchain_mcp_adapters.client import MultiServerMCPClient
async with MultiServerMCPClient(
{
"remote-claws": {
"url": "http://YOUR_IP:8080/mcp",
"transport": "streamable_http",
"headers": {
"Authorization": "Bearer YOUR_TOKEN_HERE"
}
}
}
) as client:
tools = client.get_tools()
# Use tools with your LangGraph agent
OpenAI Agents SDK
from agents import Agent
from agents.mcp import MCPServerStreamableHttp
mcp = MCPServerStreamableHttp(
url="http://YOUR_IP:8080/mcp",
headers={"Authorization": "Bearer YOUR_TOKEN_HERE"},
)
agent = Agent(
name="desktop-controller",
mcp_servers=[mcp],
)
n8n
- Add an MCP Client Tool node to your workflow
- Set the URL to
http://YOUR_IP:8080/mcp - Add a custom header:
Authorization: Bearer YOUR_TOKEN_HERE - The node auto-discovers all 4 tools (39 actions)
OpenClaw
Add to ~/.openclaw/openclaw.json:
{
"mcp": {
"servers": {
"remote-claws": {
"url": "http://YOUR_IP:8080/mcp",
"transport": "streamable-http",
"enabled": true,
"headers": {
"Authorization": "Bearer YOUR_TOKEN_HERE"
}
}
}
}
}
Then run openclaw mcp reload (or restart the gateway) — see the setup guide for the full verification workflow.
For a detailed walkthrough including skill installation and troubleshooting, see remote-claws-openclaw-setup-guide.md.
Any MCP Client
Remote Claws works with any MCP-compliant client. Connect to either transport:
Streamable HTTP (default): http://YOUR_IP:8080/mcp
SSE (legacy): http://YOUR_IP:8080/sse
Header: Authorization: Bearer YOUR_TOKEN_HERE
Configuration
Config File (remote-claws.json)
Drop a remote-claws.json in the working directory to set defaults. Every setting from the env var table below can be set here. Values support ${ENV_VAR} expansion and ${ENV_VAR:-default} fallback:
{
"host": "192.168.1.50",
"port": 9090,
"allowed_hosts": "localhost,${TAILSCALE_IP:-127.0.0.1}",
"browser_headless": false,
"screenshot_quality": 85,
"auth_file": "${HOME}/.remote-claws-auth.json"
}
Copy remote-claws.example.json as a starting point:
cp remote-claws.example.json remote-claws.json
Environment Variables
Env vars override the config file. All use the REMOTE_CLAWS_ prefix:
| Variable | Default | Description |
|---|---|---|
REMOTE_CLAWS_HOST | 0.0.0.0 | Bind address |
REMOTE_CLAWS_PORT | 8080 | Listen port |
REMOTE_CLAWS_TRANSPORT | streamable-http | MCP transport: streamable-http or legacy sse (one at a time) |
REMOTE_CLAWS_ALLOWED_IPS | (empty) | Comma-separated source IPs allowed to connect. Empty = no IP filtering (rely on token auth). Checked before auth. |
REMOTE_CLAWS_ALLOWED_HOSTS | * | Comma-separated trusted Host headers. * disables host checking. |
REMOTE_CLAWS_AUTH_FILE | .remote-claws-auth.json | Path to auth hash file |
REMOTE_CLAWS_PERMISSIONS_FILE | permissions.json | Path to permission policy |
REMOTE_CLAWS_ENABLED_GROUPS | browser,desktop,exec,files | Tool groups loaded at startup. Groups not listed are not imported and expose no tools. |
REMOTE_CLAWS_BROWSER_CHANNEL | chrome | Which browser to drive. chrome = system Google Chrome (real fingerprint, persistent profile) — almost always what you want. chromium = bundled Playwright build for CI (requires playwright install chromium; visibly automated, will trip bot walls). |
REMOTE_CLAWS_BROWSER_PROFILE_DIR | OS default | Override the Chrome user-data directory. Empty = OS-appropriate default. |
REMOTE_CLAWS_BROWSER_STEALTH | true | Apply tf-playwright-stealth patches to every page. Disable only if a site misbehaves under them. |
REMOTE_CLAWS_BROWSER_HEADLESS | false | Run Chrome headless. Strongly discouraged when browser_channel=chrome — anti-bot vendors fingerprint headless rendering. |
REMOTE_CLAWS_SCREENSHOT_MAX_WIDTH | 1280 | Max screenshot width |
REMOTE_CLAWS_SCREENSHOT_HEIGHT | 960 | Max screenshot height |
REMOTE_CLAWS_SCREENSHOT_QUALITY | 75 | JPEG quality (1-100) |
REMOTE_CLAWS_SCREENSHOT_DIR | (empty) | Audit directory for remote_browser screenshot copies (when set) |
REMOTE_CLAWS_SHOT_TTL_SECONDS | 600 | Seconds a /dl/ capability-URL download stays valid |
REMOTE_CLAWS_MAX_INLINE_BYTES | 1048576 | Max bytes remote_files read returns inline (base64) before hard-erroring with an as_url recovery |
REMOTE_CLAWS_CONFIG_FILE | remote-claws.json | Path to the JSON config file |
Priority
Highest wins: env vars → config file → built-in defaults
Troubleshooting: 421 Errors
If agents connecting over a VPN or remote IP get 421 Misdirected Request, the server is rejecting the Host header. Fix:
# Allow your Tailscale IP
REMOTE_CLAWS_ALLOWED_HOSTS="localhost,127.0.0.1,100.82.48.9" remote-claws
# Or disable host checking entirely (if you trust your network)
REMOTE_CLAWS_ALLOWED_HOSTS="*" remote-claws
Or set it in remote-claws.json:
{ "allowed_hosts": "localhost,127.0.0.1,100.82.48.9" }
Smoke Testing
scripts/smoke_browser.py connects to a running server over SSE and drives a short real-world browsing session (X.com posts, Bloomberg headlines, follow a story link). It saves a screenshot per step under ./smoke-screenshots/ so you can visually confirm that the persistent Chrome profile is signed in and that paywalls / bot walls aren't blocking you.
pip install "mcp[cli]>=1.20"
export REMOTE_CLAWS_URL="http://<remote-host>:8080/mcp"
export REMOTE_CLAWS_TOKEN="<bearer token>"
python scripts/smoke_browser.py
How It Works
The server embeds comprehensive instructions that tell connected agents how to use the tools effectively — which tool group to pick, recommended workflows, and important gotchas. Agents receive these instructions automatically when they connect via MCP.
Browser tools use Playwright with CSS selectors — reliable and resolution-independent. The browser launches lazily on first use and stays open across tool calls.
Desktop tools use pyautogui for mouse/keyboard and pywinauto for Windows UI element targeting. Agents can click by screen coordinates (from screenshots) or by element name (more reliable).
Exec tools are fully async — start a process, do other work, check back for output, send input if interactive.
Screenshots are downscaled to JPEG and returned inline so vision-capable models can see and act on them.
License
MIT