Skip to content

zackliu/remote-executor

Repository files navigation

Remote Agent

remote-agent is a minimal working implementation of a local daemon plus remote executor model.

The main v1 path that works today:

  • A remote executor establishes an outbound connection through Azure Web PubSub
  • A local daemon discovers executors through Azure Web PubSub and maintains inventory
  • The local daemon exposes a streamable HTTP MCP server
  • In normal usage, start that MCP server first, then connect Codex / Copilot to http://host:port/mcp
  • remote.run supports remote command execution on a single executor
  • remote.cancel supports cancelling long-running requests

Capabilities that are not finished yet:

  • fs.*
  • artifact.*
  • interactive session.*
  • git.*
  • advanced recovery
  • advanced permission controls

Repository Layout

packages/contracts  shared protocol / schema / registry types
packages/executor   remote executor process
packages/daemon     local daemon + MCP server

What Works Today

Current MCP tools:

  • executors.list
  • executors.get
  • executors.find
  • executor.instruction.set
  • remote.run
  • remote.cancel
  • remote.request_status

Notes:

  • There are no file-oriented MCP tools such as remote.list_dir or remote.read_file yet.
  • executors.get returns the current instruction for that executor. If it is missing and you already know what that machine is for, call executor.instruction.set once with a full replacement instruction.
  • The standard integration path is not to let Codex / Copilot spawn the stdio process directly. Start the daemon HTTP MCP server first and connect to its /mcp URL.
  • The stdio entry is still available, but it is only recommended for local development and protocol debugging.

Prerequisites

  • Node.js 22+
  • npm
  • one Azure Web PubSub resource
  • one fixed hub, currently rexec by default

For a real end-to-end test, you need at minimum:

  • a local machine that can run the daemon
  • another machine, VM, or at least another isolated working directory that can run the executor
  • the same Azure Web PubSub connection string

Install

npm install
npm run build

During development, if you want to start the executor and daemon like normal CLI tools from any directory, run this once as well:

npm link -w packages/executor
npm link -w packages/daemon

This exposes the local CLIs from both workspace packages on your global PATH:

  • ra-exec
  • ra-daemon
  • ra-mcp

After that, you can run them directly from any directory, for example:

ra-exec --executor-id exec-dev-01
ra-daemon
ra-mcp

If you update the source code, rebuild first:

npm run build

These CLI aliases now launch through a Node launcher script inside each package and then forward into the built dist output. That keeps the npm link shim stable on Windows as well, because it executes through node instead of depending on .js file associations.

Quick regression:

npm test

Configuration

Daemon Environment Variables

Required:

  • WEB_PUBSUB_CONNECTION_STRING

Common optional settings:

  • WEB_PUBSUB_HUB
  • DAEMON_ID
  • DAEMON_INVENTORY_LOG_INTERVAL_MS
  • DAEMON_REGISTRY_REFRESH_INTERVAL_MS
  • DAEMON_EXECUTOR_HEARTBEAT_INTERVAL_MS
  • DAEMON_EXECUTOR_SUSPECT_AFTER_MS
  • DAEMON_EXECUTOR_OFFLINE_AFTER_MS
  • DAEMON_TOKEN_EXPIRATION_MINUTES
  • DAEMON_VERSION
  • DAEMON_HOSTNAME
  • DAEMON_MCP_HOST
  • DAEMON_MCP_PORT
  • DAEMON_MCP_PATH
  • DAEMON_MCP_ALLOWED_HOSTS

DAEMON_MCP_* defaults:

  • DAEMON_MCP_HOST=127.0.0.1
  • DAEMON_MCP_PORT=8765
  • DAEMON_MCP_PATH=/mcp

So the default URL is:

http://127.0.0.1:8765/mcp

WEB_PUBSUB_HUB default:

  • WEB_PUBSUB_HUB=rexec

DAEMON_MCP_ALLOWED_HOSTS is a comma-separated list, for example:

localhost,127.0.0.1,daemon.example.com

Executor Environment Variables

The executor supports three connection modes:

  1. WEB_PUBSUB_CONNECTION_STRING plus optional WEB_PUBSUB_HUB
  2. EXECUTOR_CLIENT_ACCESS_URL
  3. WEB_PUBSUB_ENDPOINT plus optional WEB_PUBSUB_HUB

At the moment, the most stable and convenient real-world mode is the first one: let the executor mint its own client token locally from the connection string. WEB_PUBSUB_HUB now defaults to rexec, so that value is used automatically when not provided.

Required:

  • EXECUTOR_ID, or the startup argument --executor-id <id> / --id <id>
  • any one of the three connection modes above

Common optional settings:

  • EXECUTOR_TAGS
  • EXECUTOR_HEARTBEAT_INTERVAL_MS
  • EXECUTOR_TOKEN_EXPIRATION_MINUTES
  • EXECUTOR_VERSION
  • EXECUTOR_HOSTNAME
  • EXECUTOR_OS
  • EXECUTOR_ARCH

EXECUTOR_TAGS format:

env=dev,region=use,role=builder

Standard Startup

1. Build Once

npm install
npm run build

2. Start An Executor

Recommended: run it on another machine, in a VM, or at least from another isolated directory.

If you already ran npm link above, you can also use ra-exec directly without switching back to the repo directory.

Windows PowerShell:

$env:EXECUTOR_TAGS = "env=dev,region=local"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
node .\packages\executor\dist\main.js --executor-id exec-dev-01

Equivalent linked command:

$env:EXECUTOR_TAGS = "env=dev,region=local"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
ra-exec --executor-id exec-dev-01

Development mode:

$env:EXECUTOR_TAGS = "env=dev,region=local"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
npx tsx .\packages\executor\src\main.ts --executor-id exec-dev-01

3. Start The Streamable HTTP MCP Server

This is the standard runtime path. Codex / Copilot should connect to this HTTP MCP URL instead of launching main.js directly, and instead of defaulting to stdio.

For stable usage, running the built output is recommended:

$env:DAEMON_ID = "daemon-local-01"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
node .\packages\daemon\dist\mcp-main.js

If you already ran npm link, you can also run it directly from any directory:

$env:DAEMON_ID = "daemon-local-01"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
ra-mcp

Note: ra-daemon is bound to the plain daemon entry, which corresponds to packages/daemon/src/main.ts. It starts the command execution / diagnostics process only and does not start the HTTP MCP server. During development, if you want to start just the plain daemon as a normal command, you can run:

$env:DAEMON_ID = "daemon-local-01"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
ra-daemon

Equivalent workspace script:

$env:DAEMON_ID = "daemon-local-01"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
npm run start -w packages/daemon

Development mode:

$env:DAEMON_ID = "daemon-local-01"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
npm run dev:http -w packages/daemon

After startup, the daemon prints a log similar to this on stderr:

mcp streamable http listening
url=http://127.0.0.1:8765/mcp

4. Optional: Start The Plain Daemon Diagnostic Process

If you only want to validate discovery or direct remote run before MCP, you can start the plain daemon by itself:

$env:DAEMON_ID = "daemon-local-01"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
node .\packages\daemon\dist\main.js

This entry does not start the HTTP MCP server.

5. Optional: Use The Stdio MCP Entry During Development

The stdio entry is kept only for development and protocol debugging.

Built output:

npm run start:stdio -w packages/daemon

Development mode:

npm run dev:stdio -w packages/daemon

Codex Configuration

The normal Codex integration should point to an already-running streamable HTTP MCP URL.

A typical ~/.codex/config.toml:

[mcp_servers.remote_agent]
url = "http://127.0.0.1:8765/mcp"

If your daemon is bound elsewhere, replace that with the matching http://host:port/path.

Recommendations:

  • start the remote-agent HTTP MCP server separately first
  • then let Codex connect to that URL
  • never commit a real WEB_PUBSUB_CONNECTION_STRING
  • if you later add a reverse proxy, auth headers, or HTTPS, add the matching fields in the Codex MCP config layer

Suggested validation order:

  1. Start an executor
  2. Start mcp-main.js
  3. Confirm in Codex that remote_agent is connected
  4. Run executors.list first
  5. Then run one harmless remote.run

Recommended starter prompts:

List the currently online executors.
Run a harmless directory listing command on exec-dev-01.
Use dir /b on Windows and ls -1 on Linux/macOS.

If you already know what a specific executor is for, you can also write its instruction first:

Use executors.get for exec-dev-01.
If the executor instruction is missing, set it to a full replacement instruction that explains the executor's environment and intended use.

GitHub Copilot Configuration

GitHub Copilot should also connect to the same HTTP MCP URL.

Recommended setup:

  1. Start the remote-agent streamable HTTP MCP server separately first
  2. Add an MCP server with type = http in Copilot's MCP config
  3. Point the URL to http://127.0.0.1:8765/mcp

If you mainly use VS Code, open MCP: Open User Configuration from the command palette and configure remote-agent as a remote HTTP server. A typical config looks like this:

{
  "servers": {
    "remote-agent": {
      "type": "http",
      "url": "http://127.0.0.1:8765/mcp"
    }
  }
}

If you mainly use Copilot CLI, the idea is the same: add an HTTP MCP server and point it to the same /mcp URL.

Suggested validation order:

  1. Confirm the executor is online
  2. Confirm the daemon log already printed mcp streamable http listening
  3. Call executors.list first from Copilot
  4. Then run one harmless directory listing

Recommended test prompts:

Use the remote-agent MCP server to list available executors.
Use the remote-agent MCP server to run a harmless directory listing on executor exec-dev-01.
Use dir /b on Windows and ls -1 on Unix-like systems.

Testing

Unit And Package-Level Tests

npm test

Covers:

  • contracts schema
  • daemon registry / routing
  • daemon MCP in-memory / streamable HTTP integration
  • executor config / discovery / task runner

Build Check

npm run build

This verifies:

  • TypeScript types
  • declaration emit
  • workspace-level build health

Live Integration Test

This test starts real processes for:

  • one local streamable HTTP MCP daemon
  • one executor in a separate temporary working directory
  • one official MCP client

It then validates through Azure Web PubSub:

  • executor discovery
  • harmless directory listing
  • long-task cancellation

Run it with:

$env:REMOTE_AGENT_LIVE_WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
$env:REMOTE_AGENT_LIVE_WEB_PUBSUB_HUB = "rexec"
npm run test:integration

Manual Smoke

Smoke 1: Discovery

Start an executor first, then start the HTTP MCP daemon.

Expected:

  • the daemon log includes discovery probe published
  • the daemon log includes inventory for that executor
  • an MCP client calling executors.get can retrieve that executor

Smoke 2: Direct Remote Run Without MCP

This mode is useful when you want to rule out the MCP layer first:

$env:DAEMON_ID = "daemon-smoke-02"
$env:WEB_PUBSUB_CONNECTION_STRING = "Endpoint=...;AccessKey=...;Version=1.0;"
$env:DAEMON_RUN_EXECUTOR_ID = "exec-smoke-01"
$env:DAEMON_RUN_COMMAND = "echo hello-from-daemon"
node .\packages\daemon\dist\main.js

Expected:

  • the daemon prints remote run result
  • stdout contains hello-from-daemon

Smoke 3: Harmless MCP Run

There is no fs.list MCP tool yet, so directory listing currently goes through a command.

Windows:

Use tool remote.run with:
- executorId: exec-smoke-01
- command: dir /b

Linux/macOS:

Use tool remote.run with:
- executorId: exec-smoke-01
- command: ls -1

Expected:

  • return value has ok = true
  • stdout contains directory names

Smoke 4: MCP Cancel

Windows:

Use tool remote.run with:
- executorId: exec-smoke-01
- command: ping -n 30 127.0.0.1 >nul
- waitForCompletion: false

After you get the requestId:

Use tool remote.cancel with:
- requestId: <the request id>

Then poll:

Use tool remote.request_status with:
- requestId: <the request id>

Expected:

  • final status = completed
  • ok = false
  • exitCode = null

Troubleshooting

MCP Client Connects But No Executors Appear

Check these first:

  • whether the executor has actually started
  • whether WEB_PUBSUB_HUB matches on both sides
  • whether the executor and daemon use the same Web PubSub resource
  • whether the daemon log includes discovery probe published
  • whether the executor log includes hello published

Executor Is Discoverable But remote.run Fails

Troubleshoot it layer by layer:

  1. Bypass MCP and run the daemon directly with DAEMON_RUN_EXECUTOR_ID plus DAEMON_RUN_COMMAND
  2. If the direct path works, inspect the MCP URL configuration next
  3. If the direct path also fails, inspect executor stderr and daemon stderr

HTTP MCP Cannot Connect

Check these carefully:

  • whether mcp-main.js is actually running
  • whether DAEMON_MCP_HOST / DAEMON_MCP_PORT / DAEMON_MCP_PATH match the client configuration
  • whether the value you gave to Codex / Copilot is the full URL
  • if the daemon is bound to 0.0.0.0, the client must connect to a real reachable address, not the literal string 0.0.0.0
  • if DAEMON_MCP_ALLOWED_HOSTS is configured, whether the request host is in the allowlist

Stdio MCP Disconnects Immediately After Connecting

If you are intentionally using stdio during development, check:

  • whether you started mcp-stdio-main.js
  • whether any normal logs are being written to stdout
  • whether the command / args path is correct

Cancel Looks Like It Did Not Work

The recommended cancel flow right now is:

  1. Call remote.run with waitForCompletion: false
  2. Get the requestId
  3. Call remote.cancel
  4. Poll remote.request_status

Security Notes

  • WEB_PUBSUB_CONNECTION_STRING is a high-privilege secret and must not be committed to the repository.
  • The current default HTTP MCP address is a local unauthenticated setup, suitable for local use and development environments, not for direct exposure to the public internet.
  • If you later expose MCP through a reverse proxy or public address, add HTTPS, authentication, and access control at the proxy layer.
  • If this is later shared by multiple users, the current v1 development trust model is not a good long-term security posture.

Current Status

If you only want the shortest answer to "how should I use this right now?":

  1. npm install
  2. npm run build
  3. start one executor
  4. start node packages/daemon/dist/mcp-main.js
  5. point the MCP server in Codex or Copilot at http://127.0.0.1:8765/mcp
  6. run executors.list first
  7. then run one harmless remote.run

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors