Remote command execution relay for AI agents. Execute PowerShell commands and transfer files on remote machines through NAT/firewalls via HTTP polling. Multi-client support — a single relay can serve many target machines and route commands to a specific one by name.
+---------------------+ +----------------------+ +---------------------+
| MCP Client | | Relay Server | | Target Machines |
| (Claude Code) | | (.NET 9.0) | | (.NET 9.0) |
| | | | | |
| +---------------+ | | HTTP API :7890 | | +---------------+ |
| | MCP Server |--+-----+-> /api/exec | | | Client A | |
| | (Node.js) | | | /api/upload |<----+--| (polling) | |
| | stdio | | | /api/download | | +---------------+ |
| +---------------+ | | /api/clients |<----+--| Client B ... | |
| | | | | +---------------+ |
+---------------------+ +----------------------+ +---------------------+
| Component | Runtime | Description |
|---|---|---|
| RemoteCmd.Server | .NET 9.0 | HTTP relay server, accepts commands and proxies to the targeted client |
| RemoteCmd.Client | .NET 9.0 | Runs on each target machine, polls server, executes via PowerShell |
| RemoteCmd.Shared | .NET 9.0 | Shared Crypto (AES-256-GCM) library |
| mcp-server | Node.js | MCP (Model Context Protocol) bridge for Claude Code integration |
- Each Client on a target machine registers with a stable
clientId(persisted to disk) and a human-readablename(default:Environment.MachineName, override via--name). - Client polls Server every 800 ms for pending commands and file transfers scoped to its session.
- Controller (Claude Code via MCP, or curl) sends a command to the Server, optionally with
?client=<name|id>to target a specific machine. - The Server queues the command on that client's session, waits for the result, returns it.
Clients only need outbound HTTP. No inbound ports on the target machines.
dotnet run --project RemoteCmd.Server -- <TOKEN>
# With env vars (useful for systemd, containers, tests):
REMOTECMD_TOKEN=<TOKEN> REMOTECMD_NO_TLS=1 dotnet run --project RemoteCmd.ServerServer listens on http://0.0.0.0:7890 (or https:// with TLS). Token is used for authentication on all API endpoints.
# From source
dotnet run --project RemoteCmd.Client -- <SERVER_IP> <TOKEN> [--name <alias>]
# Or the published self-contained exe:
RemoteCmd.Client.exe <SERVER_IP> <TOKEN> --name comos-1Each client persists its GUID to %LOCALAPPDATA%\RemoteCmd\client.<name>.id (Linux/macOS: $XDG_DATA_HOME/RemoteCmd/ or ~/.local/share/RemoteCmd/). The ID survives restarts. The id file is scoped per --name, so multiple aliased instances on the same machine (e.g. elevated + non-elevated) get distinct ids and don't compete for the same session. A legacy client.id is auto-migrated for the default machine-name instance.
{
"mcpServers": {
"remote-cmd": {
"type": "stdio",
"command": "node",
"args": ["<path-to>/mcp-server/index.mjs"],
"env": {
"REMOTECMD_URL": "https://localhost:7890",
"REMOTECMD_TOKEN": "<TOKEN>",
"REMOTECMD_DEFAULT_CLIENT": "comos-1"
}
}
}
}REMOTECMD_DEFAULT_CLIENT is optional — when set, tools default to that client unless overridden via the client argument.
# List all clients
curl "http://localhost:7890/api/clients?token=<TOKEN>"
# Execute command on the single connected client (auto-select)
curl -X POST "http://localhost:7890/api/exec?token=<TOKEN>" \
-H "Content-Type: application/json" \
-d '{"command":"hostname","timeoutSeconds":30}'
# Target a specific client by name
curl -X POST "http://localhost:7890/api/exec?token=<TOKEN>&client=comos-1" \
-H "Content-Type: application/json" \
-d '{"command":"hostname"}'
# Upload file to a specific client
curl -X POST "http://localhost:7890/api/upload?token=<TOKEN>&client=comos-1&path=C:\dest\file.zip" \
--data-binary @local.zip| Tool | Description |
|---|---|
remote_list_clients |
List all known clients with connection status |
remote_status |
Check aggregate or single-client status |
remote_exec |
Execute PowerShell on a target client |
remote_upload |
Upload file from local to remote client (max 200MB) |
remote_download |
Download file from remote client to local (max 200MB) |
Every tool except remote_list_clients accepts an optional client argument (name or id). When omitted, the server auto-selects if exactly one client is connected; otherwise an error with the list of connected clients is returned.
All endpoints require a token — via ?token=<TOKEN>, X-Token: <TOKEN> header, or Authorization: Bearer <TOKEN>.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/clients |
List all clients ({count, connected, clients: [...]}) |
GET |
/api/status[?client=X] |
Aggregate status; with client returns per-client details |
POST |
/api/exec[?client=X] |
Execute command {"command":"...","timeoutSeconds":30} |
POST |
/api/upload?path=<remote>[&client=X] |
Upload file (binary body) |
GET |
/api/download?path=<remote>[&client=X] |
Download file |
Clients identify themselves via ?clientId=<guid>&name=<hostname> on every polling request.
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/poll |
Poll for pending command (encrypted) |
POST |
/api/result |
Post encrypted command result |
GET |
/api/file-poll |
Poll for pending file transfer |
GET |
/api/file-data |
Download file data for upload-to-remote |
POST |
/api/file-done |
Confirm file saved |
POST |
/api/file-upload |
Upload file data for download-from-remote |
- If
?client=<name|id>is specified → that session (404 if unknown, 400 if not connected). - Else if exactly one client is connected → that one.
- Else → error listing connected client names.
# Build + test
dotnet build RemoteCmd.sln
dotnet test RemoteCmd.sln
# MCP tests
cd mcp-server && npm install && npm test
# Publish self-contained client
dotnet publish RemoteCmd.Client -c Release -r win-x64 --self-contained \
-p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true \
-o publish/client| Variable | Where | Default | Description |
|---|---|---|---|
REMOTECMD_TOKEN |
Server, MCP | — | Shared authentication token |
REMOTECMD_NO_TLS |
Server | unset | 1/true disables TLS (fallback when --no-tls is not passed) |
REMOTECMD_URL |
MCP | https://localhost:7890 |
Relay URL |
REMOTECMD_DEFAULT_CLIENT |
MCP | unset | Name or id of client to target when client arg is omitted |
| Layer | Technology | Scope |
|---|---|---|
| Transport | TLS 1.2+ (self-signed cert) | Server ↔ Client HTTPS |
| Payload | AES-256-GCM | All commands, results, file data, metadata |
| Authentication | Shared token, constant-time comparison | All /api/* endpoints |
Token may be passed via ?token=, X-Token: header, or Authorization: Bearer. Prefer the header or Bearer form in production — query strings leak into proxy logs.
Use --no-tls (or REMOTECMD_NO_TLS=1) on the server for HTTP-only mode (AES payload encryption stays active).
| Parameter | Value |
|---|---|
| Client poll interval | 800 ms |
| Command timeout | Configurable per request (default 30 s, max 300 s) |
| Process kill timeout | 60 s (client-side) |
| File transfer timeout | 5 minutes |
| Max file size / body | 200 MB |
| Auto-reconnect | Exponential backoff (1 s → 30 s) |
| Concurrency | Per-client SemaphoreSlim(1) — each machine serial, multiple machines in parallel |
| Shell | powershell.exe -NoProfile -NonInteractive |
| Client detection | Connected if last poll < 10 s ago |
RemoteCmd.sln
├── RemoteCmd.Shared/ # Shared Crypto (AES-256-GCM)
├── RemoteCmd.Server/ # HTTPS relay server
├── RemoteCmd.Client/ # Target machine client
├── RemoteCmd.Tests/ # xUnit unit + integration tests
├── mcp-server/ # MCP bridge (Node.js)
│ └── tests/ # node --test validation tests
├── .github/workflows/ # CI + Release
│ ├── ci.yml
│ └── release.yml
└── rcmd.sh # Shell helper
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: description') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Email: dev@nks-hub.cz
- Bug reports: GitHub Issues
Private — NKS Development
Made by NKS Hub