@vo1d7bin/remote-mcp-agent 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,42 +1,66 @@
1
1
  # vo1d Remote MCP device agent
2
2
 
3
- Connect your own Windows, Linux, or macOS computer to your account on **https://mcp.thelinuxserver.cloud** using one command. This agent is independent of Desktop Commander and its hosted relay.
3
+ Connect Windows, Linux or macOS to your account at **https://mcp.thelinuxserver.cloud**. Independent of Desktop Commander.
4
4
 
5
- Requires Node.js 22.14 or newer.
5
+ Node.js 22.14+ required.
6
6
 
7
- ## One-command connection
7
+ ## Connect in one command
8
8
 
9
9
  ```powershell
10
10
  npx -y @vo1d7bin/remote-mcp-agent@latest remote
11
11
  ```
12
12
 
13
- **First run:** The agent prints a verification code and opens your default browser. Sign in with Google if needed, compare the code shown in the terminal and browser, then select **Approve device**. The agent automatically receives device credentials, stores them securely in your user profile, and connects to our relay.
13
+ First run opens a browser to approve your device through your existing Google account. Compare the verification code in the browser and terminal, then approve. The agent stores its credentials under your user profile and connects automatically. Subsequent runs restore that authorization; you do not need to pair every time.
14
14
 
15
- **Later runs:** Execute the **same command**. Previously saved device credentials are reused, so no browser approval is required. The agent reconnects automatically after temporary connection interruptions while it remains running.
15
+ A compact connection screen confirms the device name, account, unique device ID and connection status, with a short reminder to keep the terminal open.
16
16
 
17
- The terminal must remain open for the agent to remain online. We may add an installer/service or desktop tray application in a future version.
17
+ ### Commands
18
18
 
19
- By default, the agent permits **system information only**. To additionally expose names of files and directories within a specific, safe folder, use:
19
+ | Command | Purpose |
20
+ | --- | --- |
21
+ | `remote` | Connect, or authorize in browser the first time |
22
+ | `status` | Check the server-recorded online/offline state, account and device ID |
23
+ | `logout` | Revoke this device in the server and remove saved local credentials |
24
+ | `open` | Open the dashboard in your browser |
25
+ | `help` / `--help` | Show available commands and examples |
26
+ | `--version` | Display the agent version |
27
+ | `enroll` | Manual code-based pairing for advanced/headless setups |
28
+
29
+ Use the same `npx -y @vo1d7bin/remote-mcp-agent@latest` prefix for each command. For example:
20
30
 
21
31
  ```powershell
22
- npx -y @vo1d7bin/remote-mcp-agent@latest remote --allow-root "$HOME\mcp-share"
32
+ npx -y @vo1d7bin/remote-mcp-agent@latest status
33
+ npx -y @vo1d7bin/remote-mcp-agent@latest open
34
+ npx -y @vo1d7bin/remote-mcp-agent@latest logout
23
35
  ```
24
36
 
25
- The specified directory must already exist. Do not grant access to your full home directory, system drive, or secrets. Only the folder and its descendants are visible. No file contents, writes, or terminal execution are provided in this version.
37
+ `remote --help` and `remote --logout` are accepted as convenience aliases.
26
38
 
27
- ## Advanced options
39
+ **Logout revokes the device credential on the server**, disconnecting a running agent. For safety, if the relay is unreachable, the CLI preserves local credentials and tells you how to revoke through the dashboard. `logout --local` is a last-resort local cleanup only: it does **not** revoke server authorization, and you must also revoke the device in the dashboard.
40
+
41
+ ## Share a specific folder (optional)
42
+
43
+ By default, the agent exposes basic system information only. To allow a client to list names in a safe directory:
28
44
 
29
- ```text
30
- vo1d-agent remote [--allow-root PATH] [--server https://mcp.thelinuxserver.cloud]
31
- vo1d-agent enroll [--server https://mcp.thelinuxserver.cloud]
45
+ ```powershell
46
+ mkdir "$HOME\mcp-share" -Force
47
+ npx -y @vo1d7bin/remote-mcp-agent@latest remote --allow-root "$HOME\mcp-share"
32
48
  ```
33
49
 
34
- For a headless machine, run with `--no-browser`; the printed link can be opened from a browser on any other device. Use `VO1D_ALLOWED_ROOTS` for multiple approved folders, separated by your OS path delimiter (semicolon on Windows, colon on Linux).
50
+ Never use a secrets directory or the entire system drive as an allowed root. File contents, file modifications and terminal commands are **not** enabled in this version. The public MCP endpoint also remains gated until the end-to-end OAuth integration is ready.
35
51
 
36
- For a separate agent identity on the same computer, set `VO1D_CONFIG_DIR` to an independent directory **before** running `remote`. Device credentials are stored at `~/.vo1d-remote-mcp/device.json` by default. Keep this private; never commit it to source control.
52
+ The process needs to keep running to remain online. Closing the terminal makes the device offline but does not log it out.
53
+
54
+ ## Advanced options
37
55
 
38
- The legacy `enroll` command is available for manual one-time pairing codes. Open **Devices → Pair a device → Manual pairing** if the browser-based flow cannot be used.
56
+ - `--server URL`: use a custom HTTPS server (local HTTP development is allowed only for localhost)
57
+ - `--name NAME`: set a name during first-time authorization
58
+ - `--no-browser`: print the verification link for another computer instead of attempting to open a local browser
59
+ - `--allow-root PATH`: allow directory listing inside the specified safe path
60
+ - `VO1D_ALLOWED_ROOTS`: additional allowed paths separated by the system path delimiter
61
+ - `VO1D_CONFIG_DIR`: isolate a second authorized device profile
62
+ - `NO_COLOR=1`: suppress terminal ANSI colors
39
63
 
40
- Device access can be revoked at any time in your vo1d dashboard. If revoked, the stored credentials cannot reconnect and the machine must be authorized again.
64
+ Future installers can run the same background agent without requiring users to install Node themselves.
41
65
 
42
66
  Source: https://github.com/THE-L1NUX-USER/vo1d-remote-mcp
package/bin/cli-ux.mjs ADDED
@@ -0,0 +1,70 @@
1
+ const colors=Boolean(process.stdout.isTTY && !process.env.NO_COLOR && process.env.TERM!=='dumb');
2
+ const tint=(s,c)=>colors?'['+c+'m'+s+'':s;
3
+ const cyan=s=>tint(s,'96'), green=s=>tint(s,'92'), dim=s=>tint(s,'90');
4
+ export const version='0.3.0';
5
+ export function banner() {
6
+ console.log('');
7
+ console.log(cyan(' __ __ ___ _ ____'));
8
+ console.log(cyan(' \\ \\ / / / _ \\ / | | _ \\'));
9
+ console.log(cyan(' \\ V / | (_) || | | | | |'));
10
+ console.log(cyan(' \\_/ \\___/ |_| |____/'));
11
+ console.log('');
12
+ console.log(' '+cyan('REMOTE MCP')+dim(' · v'+version));
13
+ console.log(' '+dim('Your machines. Your control.'));
14
+ console.log('');
15
+ }
16
+ export function heading(title) {console.log(cyan('◆ '+title));}
17
+ export function info(label,value){console.log(' '+dim(label.padEnd(15))+String(value||'—'));}
18
+ export function success(message){console.log(green(' ✓ '+message));}
19
+ export function note(message){console.log(dim(' '+message));}
20
+ export function divider(){console.log(dim(' ───────────────────────────────────────────────────'));}
21
+ export function help() {
22
+ banner();
23
+ heading('Commands');
24
+ console.log(' remote Connect this device (default; browser approval on first run)');
25
+ console.log(' status Show this device, account and current relay status');
26
+ console.log(' logout Revoke device authorization and delete local credentials');
27
+ console.log(' open Open the online device dashboard in your browser');
28
+ console.log(' enroll Manual pairing code for headless or advanced setups');
29
+ console.log(' help Show these commands');
30
+ console.log(' --version Show CLI version');
31
+ console.log('');
32
+ heading('Useful options');
33
+ console.log(' remote --allow-root PATH Share directory names from an approved folder');
34
+ console.log(' remote --no-browser Print approval link instead of opening it');
35
+ console.log(' remote --server URL Use a custom vo1d server');
36
+ console.log(' logout --local Forget credentials without remote revocation');
37
+ console.log(' --name DEVICE_NAME Choose name when first pairing');
38
+ console.log('');
39
+ heading('Examples');
40
+ console.log(' npx -y @vo1d7bin/remote-mcp-agent@latest remote');
41
+ console.log(' npx -y @vo1d7bin/remote-mcp-agent@latest remote --allow-root ./mcp-share');
42
+ console.log(' npx -y @vo1d7bin/remote-mcp-agent@latest status');
43
+ console.log(' npx -y @vo1d7bin/remote-mcp-agent@latest logout');
44
+ console.log('');
45
+ note('Tip: keep the terminal open while you want this device online.');
46
+ note('Only system information is shared unless you specify an allowed folder.');
47
+ }
48
+ export function ready(details) {
49
+ console.log('');
50
+ divider();
51
+ success('Secure connection established');
52
+ success('Device is online');
53
+ console.log('');
54
+ info('Device',details.name);
55
+ info('Account',details.account||'Your vo1d workspace');
56
+ info('Device ID',details.id);
57
+ info('Server',details.server);
58
+ info('Status','Online');
59
+ info('Allowed roots',details.roots?.length?details.roots.join(' ; '):'None (system info only)');
60
+ console.log('');
61
+ heading('Next');
62
+ note('Return to your MCP-compatible assistant when the connection is enabled.');
63
+ note('Keep this terminal open, or minimize it.');
64
+ note('Press Ctrl+C to disconnect (not to log out).');
65
+ console.log('');
66
+ heading('Manage this device (in another terminal)');
67
+ note('Status: npx -y @vo1d7bin/remote-mcp-agent@latest status');
68
+ note('Log out: npx -y @vo1d7bin/remote-mcp-agent@latest logout');
69
+ console.log('');
70
+ }
@@ -0,0 +1,104 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { spawn } from 'node:child_process';
5
+ import { banner, heading, info, success, note } from './cli-ux.mjs';
6
+
7
+ const profile = process.env.VO1D_CONFIG_DIR
8
+ ? path.resolve(process.env.VO1D_CONFIG_DIR)
9
+ : path.join(os.homedir(), '.vo1d-remote-mcp');
10
+ const file=path.join(profile,'device.json');
11
+ const publicServer=process.env.VO1D_SERVER || 'https://mcp.thelinuxserver.cloud';
12
+
13
+ async function readProfile(){
14
+ let cfg;
15
+ try {cfg=JSON.parse(await fs.readFile(file,'utf8'));}
16
+ catch(error) {
17
+ if(error.code==='ENOENT')return null;
18
+ throw new Error('Unable to read device credentials: '+error.message);
19
+ }
20
+ if(typeof cfg.id!=='string' || typeof cfg.token!=='string' || cfg.token.length<30 ||
21
+ typeof cfg.server!=='string')throw new Error('Saved device credentials appear corrupted.');
22
+ const server=new URL(cfg.server);
23
+ if(server.protocol!=='https:' &&
24
+ !(server.protocol==='http:' && ['localhost','127.0.0.1'].includes(server.hostname)))
25
+ throw new Error('Saved profile has an invalid server address.');
26
+ return cfg;
27
+ }
28
+ async function call(cfg,endpoint,method='GET') {
29
+ const url=new URL(endpoint,cfg.server).href;
30
+ const response=await fetch(url,{method,headers:{Authorization:'Bearer '+cfg.token},signal:AbortSignal.timeout(12000)});
31
+ if(response.status===401)return {invalid:true};
32
+ if(!response.ok)throw new Error('Server responded HTTP '+response.status);
33
+ return response.json();
34
+ }
35
+ export async function showStatus(){
36
+ banner();
37
+ heading('Device status');
38
+ const cfg=await readProfile();
39
+ if(!cfg){
40
+ note('No saved device authorization for this profile.');
41
+ note('Start with: npx -y @vo1d7bin/remote-mcp-agent@latest remote');
42
+ return;
43
+ }
44
+ info('Device ID',cfg.id);
45
+ info('Server',cfg.server);
46
+ try {
47
+ const remote=await call(cfg,'/api/agent/me');
48
+ if(remote.invalid){
49
+ info('Status','Authorization invalid or revoked');
50
+ note('Use logout --local to clear this profile, then remote to reauthorize.');
51
+ process.exitCode=1;
52
+ return;
53
+ }
54
+ info('Device',remote.name);
55
+ info('Account',remote.account || 'vo1d account');
56
+ info('Status',remote.online?'Online':'Offline');
57
+ if(!remote.online)note('Run remote to bring this device online.');
58
+ }catch(error){
59
+ info('Status','Unknown (server unavailable)');
60
+ note(error.message);
61
+ process.exitCode=1;
62
+ }
63
+ }
64
+ export async function logOut(){
65
+ banner();
66
+ heading('Log out this device');
67
+ const cfg=await readProfile();
68
+ if(!cfg){note('Already logged out. No device credentials found.');return;}
69
+ if(process.argv.includes('--local')){
70
+ await fs.unlink(file);
71
+ note('Local credentials deleted, but server authorization was NOT revoked.');
72
+ note('Revoke this device in the online dashboard if it is still authorized.');
73
+ return;
74
+ }
75
+ try {
76
+ const result=await call(cfg,'/api/agent/revoke-self','POST');
77
+ if(!result.revoked && !result.invalid)throw new Error('Server did not confirm revocation');
78
+ }catch(error){
79
+ console.error('Could not revoke device: '+error.message);
80
+ note('Credentials have been kept so you can retry safely.');
81
+ note('Alternatively, revoke from the dashboard. --local only deletes locally.');
82
+ process.exitCode=1;
83
+ return;
84
+ }
85
+ await fs.unlink(file);
86
+ success('Server authorization revoked');
87
+ success('Local device credentials removed');
88
+ note('Run remote whenever you want to authorize this machine again.');
89
+ }
90
+ export async function openDashboard(){
91
+ banner();
92
+ const cfg=await readProfile();
93
+ const origin=cfg?.server || publicServer;
94
+ const url=new URL(origin);
95
+ if(url.protocol!=='https:' && !(url.protocol==='http:'&&['localhost','127.0.0.1'].includes(url.hostname)))
96
+ throw new Error('Dashboard URL must be HTTPS');
97
+ const address=url.origin+'/dashboard';
98
+ console.log('Dashboard:',address);
99
+ if(process.argv.includes('--no-browser') || process.env.VO1D_NO_BROWSER==='true')return;
100
+ const command=process.platform==='win32'?'explorer.exe':process.platform==='darwin'?'open':'xdg-open';
101
+ const child=spawn(command,[address],{stdio:'ignore',detached:true,windowsHide:true});
102
+ child.on('error',()=>note('Could not open browser; use the URL above.'));
103
+ child.unref();
104
+ }
@@ -6,6 +6,8 @@ import path from 'node:path';
6
6
  import readline from 'node:readline/promises';
7
7
  import { spawn } from 'node:child_process';
8
8
  import { setTimeout as sleep } from 'node:timers/promises';
9
+ import { banner, heading, info, success, note, divider, help, ready, version } from './cli-ux.mjs';
10
+ import { showStatus, logOut, openDashboard } from './management.mjs';
9
11
 
10
12
  const folder = process.env.VO1D_CONFIG_DIR
11
13
  ? path.resolve(process.env.VO1D_CONFIG_DIR)
@@ -145,6 +147,8 @@ async function directoryListing(requestPath,roots) {
145
147
  }))};
146
148
  }
147
149
  async function remote() {
150
+ banner();
151
+ heading('Remote connection');
148
152
  const roots=await allowedRoots();
149
153
  const cfg=await loadOrEnroll();
150
154
  const server=normalizedServer(cfg.server);
@@ -155,7 +159,22 @@ async function remote() {
155
159
  async function connect() {
156
160
  if(stopped)return;
157
161
  const ws=new WebSocket(endpoint,{headers:{Authorization:'Bearer '+cfg.token},handshakeTimeout:12000});
158
- ws.on('open',()=>{retry=1000;console.log('Connected to',server);});
162
+ ws.on('open',async()=>{
163
+ retry=1000;
164
+ console.log('Connected to',server);
165
+ let details={id:cfg.id,name:os.hostname(),account:null,server,roots};
166
+ try {
167
+ const response=await fetch(server+'/api/agent/me',{
168
+ headers:{Authorization:'Bearer '+cfg.token},
169
+ signal:AbortSignal.timeout(9000)
170
+ });
171
+ if(response.ok){
172
+ const remote=await response.json();
173
+ details={...details,name:remote.name||details.name,account:remote.account};
174
+ }
175
+ }catch{note('Account details unavailable; relay connection is still active.');}
176
+ if(ws.readyState===WebSocket.OPEN)ready(details);
177
+ });
159
178
  ws.on('message',async raw=>{
160
179
  let task;
161
180
  try{task=JSON.parse(raw.toString());}catch{return;}
@@ -188,8 +207,12 @@ async function remote() {
188
207
  await connect();
189
208
  }
190
209
  const command=process.argv[2];
191
- if(command==='enroll')legacyEnroll().catch(error=>{console.error(error.message);process.exitCode=1;});
192
- else if(command==='remote')remote().catch(error=>{console.error(error.message);process.exitCode=1;});
193
- else console.log('Usage: vo1d-agent remote [--allow-root PATH] [--server https://mcp.thelinuxserver.cloud]\n' +
194
- ' vo1d-agent enroll (manual pairing for headless environments)\n' +
195
- 'The remote command opens a browser on first run and reconnects on subsequent runs.');
210
+ const run = fn => fn().catch(error=>{console.error('Error:',error.message);process.exitCode=1;});
211
+ if(command==='--version' || command==='-v' || command==='version') console.log('vo1d-agent v'+version);
212
+ else if(command==='--help' || command==='-h' || command==='help' || command==='remote' && process.argv.includes('--help')) help();
213
+ else if(command==='logout' || command==='remote' && process.argv.includes('--logout')) run(logOut);
214
+ else if(command==='status' || command==='whoami') run(showStatus);
215
+ else if(command==='open' || command==='dashboard') run(openDashboard);
216
+ else if(command==='enroll') run(legacyEnroll);
217
+ else if(!command || command==='remote') run(remote);
218
+ else {console.error('Unknown command:',command);help();process.exitCode=1;}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vo1d7bin/remote-mcp-agent",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Independent vo1d Remote MCP device agent for Windows, Linux and macOS",
5
5
  "type": "module",
6
6
  "license": "MIT",