@remcp/remcp 0.1.2 → 0.1.4
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 +54 -107
- package/package.json +12 -52
- package/src/agent.mjs +19 -24
- package/src/cli.mjs +42 -19
- package/src/runtime.mjs +15 -0
- package/src/version.mjs +0 -2
- package/THIRD_PARTY_NOTICES.md +0 -7
- package/assets/remcp-icon.png +0 -0
- package/mcp.json +0 -9
- package/plugin.json +0 -37
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/app.js +0 -95
- package/public/apple-touch-icon.png +0 -0
- package/public/assets/art/hero-relay.webp +0 -0
- package/public/assets/art/relay-detail.webp +0 -0
- package/public/assets/icon-120.png +0 -0
- package/public/assets/icon-16.png +0 -0
- package/public/assets/icon-180.png +0 -0
- package/public/assets/icon-192.png +0 -0
- package/public/assets/icon-256.png +0 -0
- package/public/assets/icon-32.png +0 -0
- package/public/assets/icon-48.png +0 -0
- package/public/assets/icon-512.png +0 -0
- package/public/assets/og-remcp.png +0 -0
- package/public/authorize.html +0 -70
- package/public/copy.js +0 -49
- package/public/docs.html +0 -94
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon-48x48.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +0 -154
- package/public/oauth-app.js +0 -54
- package/public/privacy.html +0 -60
- package/public/remcp-darkmode-16x16.png +0 -0
- package/public/remcp-darkmode-192x192.png +0 -0
- package/public/remcp-darkmode-32x32.png +0 -0
- package/public/remcp-darkmode-48x48.png +0 -0
- package/public/remcp-darkmode-512x512.png +0 -0
- package/public/security.html +0 -57
- package/public/site.webmanifest +0 -23
- package/public/style.css +0 -259
- package/public/support.html +0 -59
- package/public/terms.html +0 -60
- package/skills/remcp-operator/SKILL.md +0 -36
- package/src/auth.mjs +0 -71
- package/src/config.mjs +0 -51
- package/src/db.mjs +0 -102
- package/src/mcp.mjs +0 -95
- package/src/oauth.mjs +0 -164
- package/src/relay.mjs +0 -97
- package/src/review-sandbox.mjs +0 -96
- package/src/server.mjs +0 -159
- package/src/tool-catalog.json +0 -796
- package/src/util.mjs +0 -24
package/README.md
CHANGED
|
@@ -1,119 +1,82 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./assets/remcp-logo.png" width="168" alt="ReMCP logo">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">ReMCP</h1>
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
- MCP endpoint: `https://remcp.delio24.com/mcp`
|
|
7
|
-
- npm: `@remcp/remcp`
|
|
8
|
-
- License: MIT
|
|
7
|
+
<p align="center"><strong>Your computer. Your tools. One secure MCP connection.</strong></p>
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/@remcp/remcp"><img alt="npm" src="https://img.shields.io/npm/v/%40remcp%2Fremcp?style=flat-square&label=npm"></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@remcp/remcp"><img alt="downloads" src="https://img.shields.io/npm/dm/%40remcp%2Fremcp?style=flat-square&label=downloads"></a>
|
|
12
|
+
<img alt="Node.js" src="https://img.shields.io/node/v/%40remcp%2Fremcp?style=flat-square&label=node">
|
|
13
|
+
<a href="./LICENSE"><img alt="license" src="https://img.shields.io/npm/l/%40remcp%2Fremcp?style=flat-square"></a>
|
|
14
|
+
<a href="https://github.com/antonbaider/remcp/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/antonbaider/remcp?style=flat-square"></a>
|
|
15
|
+
</p>
|
|
11
16
|
|
|
12
|
-
|
|
17
|
+
<p align="center">
|
|
18
|
+
<a href="https://remcp.delio24.com">Website</a> ·
|
|
19
|
+
<a href="https://remcp.delio24.com/docs">Docs</a> ·
|
|
20
|
+
<a href="https://remcp.delio24.com/security">Security</a> ·
|
|
21
|
+
<a href="https://remcp.delio24.com/support">Support</a>
|
|
22
|
+
</p>
|
|
13
23
|
|
|
14
|
-
|
|
15
|
-
2. Run it on the computer you want to connect:
|
|
24
|
+
ReMCP is the lightweight device client that connects a computer you own or administer to your ReMCP workspace. The agent makes an outbound connection only; you do not need to expose an inbound port on the machine.
|
|
16
25
|
|
|
17
|
-
|
|
18
|
-
npx --yes @remcp/remcp@latest connect \
|
|
19
|
-
--server https://remcp.delio24.com \
|
|
20
|
-
--code YOUR_PAIRING_CODE \
|
|
21
|
-
--install
|
|
22
|
-
```
|
|
26
|
+
## Install
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
**Requires Node.js 22.5 or newer.**
|
|
25
29
|
|
|
26
30
|
```bash
|
|
27
|
-
|
|
28
|
-
remcp status
|
|
31
|
+
npm install --global @remcp/remcp@latest
|
|
29
32
|
```
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
```text
|
|
34
|
-
https://remcp.delio24.com/mcp
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Authentication uses OAuth authorization code + PKCE with rotating refresh credentials.
|
|
38
|
-
|
|
39
|
-
## Update
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
remcp update
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
`remcp update` refreshes the global CLI and compatible local runtime, then restarts the installed user service.
|
|
46
|
-
|
|
47
|
-
## CLI
|
|
34
|
+
Then open **[ReMCP → Connect a machine](https://remcp.delio24.com/app/connect)** and generate a one-time pairing command. Run that exact command on the computer you want to connect.
|
|
48
35
|
|
|
49
|
-
|
|
50
|
-
remcp start Run the device agent in the foreground
|
|
51
|
-
remcp status Show version, pairing and server health
|
|
52
|
-
remcp doctor Alias for status
|
|
53
|
-
remcp update Update ReMCP and restart the user service
|
|
54
|
-
remcp install Install or repair the user service
|
|
55
|
-
remcp uninstall Remove the user service
|
|
56
|
-
remcp uninstall --purge Remove the service and global ReMCP packages
|
|
57
|
-
remcp --version Print the installed CLI version
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Architecture
|
|
61
|
-
|
|
62
|
-
```text
|
|
63
|
-
ChatGPT / Codex / MCP client
|
|
64
|
-
│
|
|
65
|
-
│ OAuth 2.1 + HTTPS
|
|
66
|
-
▼
|
|
67
|
-
ReMCP relay /mcp
|
|
68
|
-
│
|
|
69
|
-
│ authenticated WSS (outbound from device)
|
|
70
|
-
▼
|
|
71
|
-
paired computer
|
|
72
|
-
│
|
|
73
|
-
└── local file / search / terminal / process runtime
|
|
74
|
-
```
|
|
36
|
+
> Pairing codes are generated in the authenticated workspace, expire automatically, and are single-use. Do not invent or reuse a code from documentation.
|
|
75
37
|
|
|
76
|
-
|
|
38
|
+
## Commands
|
|
77
39
|
|
|
78
|
-
|
|
40
|
+
| Command | Purpose |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `remcp start` | Start the device agent in the foreground |
|
|
43
|
+
| `remcp status` | Show pairing and service health |
|
|
44
|
+
| `remcp doctor` | Run the same connectivity diagnostics in JSON form |
|
|
45
|
+
| `remcp install` | Install the persistent Linux user service |
|
|
46
|
+
| `remcp update` | Update ReMCP and the compatible local runtime |
|
|
47
|
+
| `remcp uninstall` | Stop and remove the user service |
|
|
48
|
+
| `remcp uninstall --purge` | Remove the service and global packages |
|
|
49
|
+
| `remcp --version` | Print the installed client version |
|
|
79
50
|
|
|
80
|
-
|
|
51
|
+
## How pairing works
|
|
81
52
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- `submission/openai-plugin-review.json` — generated tool annotations plus five positive and three negative review cases.
|
|
53
|
+
1. Sign in to your ReMCP workspace.
|
|
54
|
+
2. Generate a one-time pairing command.
|
|
55
|
+
3. Run the command on your machine.
|
|
56
|
+
4. ReMCP stores the device credential locally with restrictive permissions.
|
|
57
|
+
5. The agent establishes an outbound WebSocket connection to the configured ReMCP service.
|
|
88
58
|
|
|
89
|
-
|
|
59
|
+
For the official service, compatible runtime metadata is delivered as part of the pairing response. A custom ReMCP server must be explicitly trusted with `--trust-runtime` before the client accepts runtime metadata from it.
|
|
90
60
|
|
|
91
61
|
## Security model
|
|
92
62
|
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
99
|
-
- MCP tools carry explicit `readOnlyHint`, `openWorldHint`, and `destructiveHint` annotations;
|
|
100
|
-
- the public review fixture is isolated per authenticated account and intentionally restricts terminal commands;
|
|
101
|
-
- the supplied server container runs read-only with dropped Linux capabilities.
|
|
63
|
+
- Outbound-only device connection.
|
|
64
|
+
- Per-device revocable credential.
|
|
65
|
+
- Local client configuration stored with restrictive permissions.
|
|
66
|
+
- Runtime metadata validated before installation.
|
|
67
|
+
- Custom servers require an explicit runtime trust decision.
|
|
68
|
+
- No pairing secrets belong in issues, screenshots, logs, or documentation.
|
|
102
69
|
|
|
103
|
-
|
|
70
|
+
If a machine should no longer be connected, revoke it from the workspace and remove the local service.
|
|
104
71
|
|
|
105
|
-
##
|
|
72
|
+
## Update
|
|
106
73
|
|
|
107
74
|
```bash
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# Fill in your public URL and Firebase Web App configuration.
|
|
112
|
-
docker compose up -d --build
|
|
75
|
+
remcp update
|
|
76
|
+
remcp --version
|
|
77
|
+
remcp status
|
|
113
78
|
```
|
|
114
79
|
|
|
115
|
-
Put a TLS reverse proxy in front of ReMCP and proxy WebSocket upgrades for `/agent`. The supplied Compose configuration binds the application to localhost by default. ReMCP verifies Firebase ID tokens using Google's public signing keys and does not require a Firebase Admin private key.
|
|
116
|
-
|
|
117
80
|
## Development
|
|
118
81
|
|
|
119
82
|
```bash
|
|
@@ -124,22 +87,6 @@ npm audit --omit=dev
|
|
|
124
87
|
npm pack --dry-run
|
|
125
88
|
```
|
|
126
89
|
|
|
127
|
-
For server changes:
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
docker build -t remcp:test .
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for contribution and security rules.
|
|
134
|
-
|
|
135
|
-
## Project docs
|
|
136
|
-
|
|
137
|
-
- [`docs/OPENAI_PLUGIN.md`](docs/OPENAI_PLUGIN.md) — OpenAI Plugins submission
|
|
138
|
-
- [`SECURITY.md`](SECURITY.md) — vulnerability reporting and operational security
|
|
139
|
-
- [`CONTRIBUTING.md`](CONTRIBUTING.md) — development checks
|
|
140
|
-
- [`CHANGELOG.md`](CHANGELOG.md) — release notes
|
|
141
|
-
- [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) — third-party license notices
|
|
142
|
-
|
|
143
90
|
## License
|
|
144
91
|
|
|
145
|
-
|
|
92
|
+
MIT © ReMCP contributors. See [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,64 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remcp/remcp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Secure ReMCP device client for connecting your computer to MCP over an outbound-only agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://remcp.delio24.com",
|
|
8
|
-
"bin": {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"engines": {
|
|
12
|
-
"node": ">=22.5.0"
|
|
13
|
-
},
|
|
14
|
-
"files": [
|
|
15
|
-
"bin",
|
|
16
|
-
"src",
|
|
17
|
-
"public",
|
|
18
|
-
"plugin.json",
|
|
19
|
-
"mcp.json",
|
|
20
|
-
"assets",
|
|
21
|
-
"skills/remcp-operator",
|
|
22
|
-
"LICENSE",
|
|
23
|
-
"THIRD_PARTY_NOTICES.md",
|
|
24
|
-
"README.md"
|
|
25
|
-
],
|
|
8
|
+
"bin": { "remcp": "bin/remcp.mjs" },
|
|
9
|
+
"engines": { "node": ">=22.5.0" },
|
|
10
|
+
"files": ["bin", "src", "LICENSE", "README.md"],
|
|
26
11
|
"scripts": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"check": "node --check src/server.mjs && node --check src/agent.mjs && node --check src/cli.mjs",
|
|
30
|
-
"test": "node --test test/*.test.mjs",
|
|
31
|
-
"submission": "node scripts/generate-submission.mjs"
|
|
12
|
+
"check": "node --check bin/remcp.mjs && node --check src/cli.mjs && node --check src/agent.mjs && node --check src/runtime.mjs && node --check src/version.mjs",
|
|
13
|
+
"test": "node --test test/*.test.mjs"
|
|
32
14
|
},
|
|
33
15
|
"dependencies": {
|
|
34
16
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
35
|
-
"express": "^5.2.1",
|
|
36
|
-
"express-rate-limit": "^8.7.0",
|
|
37
|
-
"helmet": "^8.3.0",
|
|
38
|
-
"jose": "^6.2.12",
|
|
39
17
|
"ws": "^8.21.3"
|
|
40
18
|
},
|
|
41
|
-
"overrides": {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
},
|
|
45
|
-
"
|
|
46
|
-
"type": "git",
|
|
47
|
-
"url": "git+https://github.com/antonbaider/remcp.git"
|
|
48
|
-
},
|
|
49
|
-
"bugs": {
|
|
50
|
-
"url": "https://github.com/antonbaider/remcp/issues"
|
|
51
|
-
},
|
|
52
|
-
"publishConfig": {
|
|
53
|
-
"access": "public"
|
|
54
|
-
},
|
|
55
|
-
"keywords": [
|
|
56
|
-
"mcp",
|
|
57
|
-
"remote",
|
|
58
|
-
"computer",
|
|
59
|
-
"filesystem",
|
|
60
|
-
"terminal",
|
|
61
|
-
"chatgpt",
|
|
62
|
-
"self-hosted"
|
|
63
|
-
]
|
|
19
|
+
"overrides": { "sharp": "0.35.4", "uuid": "11.1.1" },
|
|
20
|
+
"repository": { "type": "git", "url": "git+https://github.com/antonbaider/remcp.git" },
|
|
21
|
+
"bugs": { "url": "https://github.com/antonbaider/remcp/issues" },
|
|
22
|
+
"publishConfig": { "access": "public" },
|
|
23
|
+
"keywords": ["mcp", "model-context-protocol", "remote", "computer", "cli", "agent", "chatgpt", "codex"]
|
|
64
24
|
}
|
package/src/agent.mjs
CHANGED
|
@@ -2,23 +2,26 @@ import os from 'node:os';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import process from 'node:process';
|
|
5
|
-
import {
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
6
|
import WebSocket from 'ws';
|
|
7
7
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
8
8
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
9
|
+
import { normalizeRuntime } from './runtime.mjs';
|
|
9
10
|
import { VERSION } from './version.mjs';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
13
|
+
|
|
14
|
+
function globalNodeModules() {
|
|
15
|
+
const result = spawnSync(npmCommand, ['root', '--global'], { encoding: 'utf8' });
|
|
16
|
+
if (result.error || result.status !== 0) throw new Error('Could not locate the global npm modules directory');
|
|
17
|
+
return String(result.stdout || '').trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function localRuntimeEntry(runtimeValue) {
|
|
21
|
+
const runtime = normalizeRuntime(runtimeValue);
|
|
22
|
+
const candidate = path.join(globalNodeModules(), ...runtime.packageName.split('/'), ...runtime.entry.split(/[\\/]+/));
|
|
23
|
+
if (!existsSync(candidate)) throw new Error('ReMCP local runtime is not installed. Run `remcp install`.');
|
|
24
|
+
return candidate;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export async function runAgent(options) {
|
|
@@ -33,9 +36,9 @@ export async function runAgent(options) {
|
|
|
33
36
|
let activeSocket;
|
|
34
37
|
|
|
35
38
|
const mcp = new Client({ name: 'remcp-agent', version: VERSION });
|
|
36
|
-
const transport = new StdioClientTransport({ command: process.execPath, args: [
|
|
39
|
+
const transport = new StdioClientTransport({ command: process.execPath, args: [localRuntimeEntry(options.runtime)] });
|
|
37
40
|
await mcp.connect(transport);
|
|
38
|
-
console.log('
|
|
41
|
+
console.log('ReMCP local runtime ready');
|
|
39
42
|
|
|
40
43
|
async function respond(ws, message) {
|
|
41
44
|
try {
|
|
@@ -56,7 +59,7 @@ export async function runAgent(options) {
|
|
|
56
59
|
activeSocket = ws;
|
|
57
60
|
ws.on('open', () => {
|
|
58
61
|
ws.send(JSON.stringify({ type: 'hello', deviceId, deviceName, hostname: os.hostname(), platform: process.platform, arch: process.arch, agentVersion: VERSION }));
|
|
59
|
-
console.log(`Connected to ${
|
|
62
|
+
console.log(`Connected to ${serverUrl} as ${deviceName}`);
|
|
60
63
|
});
|
|
61
64
|
ws.on('message', raw => {
|
|
62
65
|
let message;
|
|
@@ -74,17 +77,9 @@ export async function runAgent(options) {
|
|
|
74
77
|
try { await mcp.close(); } catch {}
|
|
75
78
|
try { await transport.close(); } catch {}
|
|
76
79
|
}
|
|
80
|
+
|
|
77
81
|
process.once('SIGINT', () => void stop().finally(() => process.exit(0)));
|
|
78
82
|
process.once('SIGTERM', () => void stop().finally(() => process.exit(0)));
|
|
79
83
|
connect();
|
|
80
84
|
return { stop };
|
|
81
85
|
}
|
|
82
|
-
|
|
83
|
-
if (import.meta.url === `file://${process.argv[1]}` && process.env.REMCP_SERVER_URL) {
|
|
84
|
-
await runAgent({
|
|
85
|
-
serverUrl: process.env.REMCP_SERVER_URL,
|
|
86
|
-
deviceToken: process.env.REMCP_DEVICE_TOKEN,
|
|
87
|
-
deviceId: process.env.REMCP_DEVICE_ID,
|
|
88
|
-
deviceName: process.env.REMCP_DEVICE_NAME,
|
|
89
|
-
});
|
|
90
|
-
}
|
package/src/cli.mjs
CHANGED
|
@@ -4,13 +4,15 @@ import path from 'node:path';
|
|
|
4
4
|
import process from 'node:process';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
6
|
import { runAgent } from './agent.mjs';
|
|
7
|
-
import {
|
|
7
|
+
import { normalizeRuntime } from './runtime.mjs';
|
|
8
|
+
import { PACKAGE_NAME, VERSION } from './version.mjs';
|
|
8
9
|
|
|
9
10
|
const home = os.homedir();
|
|
10
11
|
const configDir = process.env.REMCP_CONFIG_DIR || path.join(home, '.config', 'remcp');
|
|
11
12
|
const configFile = path.join(configDir, 'config.json');
|
|
12
13
|
const serviceFile = path.join(home, '.config', 'systemd', 'user', 'remcp-agent.service');
|
|
13
14
|
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
15
|
+
const officialOrigin = 'https://remcp.delio24.com';
|
|
14
16
|
|
|
15
17
|
function parse(argv) {
|
|
16
18
|
const [command = 'help', ...rest] = argv;
|
|
@@ -37,9 +39,14 @@ function output(command, args) {
|
|
|
37
39
|
return String(result.stdout || '').trim();
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
function loadConfig() {
|
|
41
|
-
if (!fs.existsSync(configFile))
|
|
42
|
-
|
|
42
|
+
function loadConfig(required = true) {
|
|
43
|
+
if (!fs.existsSync(configFile)) {
|
|
44
|
+
if (!required) return undefined;
|
|
45
|
+
throw new Error(`ReMCP is not paired. Generate a pairing command at ${officialOrigin}/app/connect`);
|
|
46
|
+
}
|
|
47
|
+
const value = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
48
|
+
value.runtime = normalizeRuntime(value.runtime);
|
|
49
|
+
return value;
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
function saveConfig(value) {
|
|
@@ -57,8 +64,8 @@ function globalCliPath() {
|
|
|
57
64
|
return process.platform === 'win32' ? path.join(prefix, 'remcp.cmd') : path.join(prefix, 'bin', 'remcp');
|
|
58
65
|
}
|
|
59
66
|
|
|
60
|
-
function npmGlobalInstall(
|
|
61
|
-
run(npmCommand, ['install', '--global',
|
|
67
|
+
function npmGlobalInstall(...specs) {
|
|
68
|
+
run(npmCommand, ['install', '--global', ...specs, '--no-audit', '--no-fund', '--loglevel=error']);
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
function quoteSystemd(value) {
|
|
@@ -73,10 +80,10 @@ function installLinuxService(cliPath = globalCliPath()) {
|
|
|
73
80
|
run('systemctl', ['--user', 'enable', '--now', 'remcp-agent.service']);
|
|
74
81
|
}
|
|
75
82
|
|
|
76
|
-
function installPersistentAgent() {
|
|
83
|
+
function installPersistentAgent(config) {
|
|
77
84
|
if (process.platform !== 'linux') throw new Error('Automatic background service installation currently supports Linux');
|
|
78
|
-
console.log(`Installing ${
|
|
79
|
-
npmGlobalInstall(`${PACKAGE_NAME}@${VERSION}
|
|
85
|
+
console.log(`Installing ReMCP ${VERSION}…`);
|
|
86
|
+
npmGlobalInstall(`${PACKAGE_NAME}@${VERSION}`, config.runtime.packageSpec);
|
|
80
87
|
installLinuxService(globalCliPath());
|
|
81
88
|
console.log('ReMCP is installed as a user service. Future updates: remcp update');
|
|
82
89
|
}
|
|
@@ -94,8 +101,15 @@ function uninstallLinuxService() {
|
|
|
94
101
|
spawnSync('systemctl', ['--user', 'daemon-reload'], { stdio: 'inherit' });
|
|
95
102
|
}
|
|
96
103
|
|
|
104
|
+
function assertRuntimeTrust(server, flags) {
|
|
105
|
+
const origin = new URL(server).origin;
|
|
106
|
+
if (origin !== officialOrigin && !flags['trust-runtime']) {
|
|
107
|
+
throw new Error('Custom servers can provide local runtime metadata. Re-run with --trust-runtime only if you trust that server.');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
97
111
|
function printHelp() {
|
|
98
|
-
console.log(`ReMCP ${VERSION}\n\nCommands:\n
|
|
112
|
+
console.log(`ReMCP ${VERSION}\n\nCommands:\n remcp start\n remcp status\n remcp doctor\n remcp update\n remcp install\n remcp uninstall\n remcp uninstall --purge\n remcp --version\n\nPairing commands are generated in the ReMCP workspace.`);
|
|
99
113
|
}
|
|
100
114
|
|
|
101
115
|
export async function main(argv = process.argv.slice(2)) {
|
|
@@ -108,8 +122,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
108
122
|
|
|
109
123
|
if (command === 'connect') {
|
|
110
124
|
const server = String(flags.server || '').replace(/\/$/, '');
|
|
111
|
-
const code = String(flags.code || '').toUpperCase();
|
|
125
|
+
const code = String(flags.code || '').replace(/\s+/g, '').toUpperCase();
|
|
112
126
|
if (!server || !code) throw new Error('--server and --code are required');
|
|
127
|
+
assertRuntimeTrust(server, flags);
|
|
113
128
|
const response = await fetch(`${server}/api/pair/claim`, {
|
|
114
129
|
method: 'POST',
|
|
115
130
|
headers: { 'content-type': 'application/json' },
|
|
@@ -117,9 +132,16 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
117
132
|
});
|
|
118
133
|
if (!response.ok) throw new Error(`Pairing failed (${response.status}): ${await response.text()}`);
|
|
119
134
|
const paired = await response.json();
|
|
120
|
-
|
|
135
|
+
const config = {
|
|
136
|
+
serverUrl: server,
|
|
137
|
+
deviceId: paired.deviceId,
|
|
138
|
+
deviceToken: paired.deviceToken,
|
|
139
|
+
deviceName: String(flags.name || os.hostname()),
|
|
140
|
+
runtime: normalizeRuntime(paired.runtime),
|
|
141
|
+
};
|
|
142
|
+
saveConfig(config);
|
|
121
143
|
console.log(`Paired ${os.hostname()} with ${server}`);
|
|
122
|
-
if (flags.install) installPersistentAgent();
|
|
144
|
+
if (flags.install) installPersistentAgent(config);
|
|
123
145
|
return;
|
|
124
146
|
}
|
|
125
147
|
|
|
@@ -136,14 +158,14 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
136
158
|
}
|
|
137
159
|
|
|
138
160
|
if (command === 'install') {
|
|
139
|
-
loadConfig();
|
|
140
|
-
installPersistentAgent();
|
|
161
|
+
installPersistentAgent(loadConfig());
|
|
141
162
|
return;
|
|
142
163
|
}
|
|
143
164
|
|
|
144
165
|
if (command === 'update') {
|
|
145
|
-
|
|
146
|
-
|
|
166
|
+
const cfg = loadConfig();
|
|
167
|
+
console.log('Updating ReMCP to the latest published version…');
|
|
168
|
+
npmGlobalInstall(`${PACKAGE_NAME}@latest`, cfg.runtime.packageSpec);
|
|
147
169
|
restartLinuxServiceIfInstalled();
|
|
148
170
|
console.log('ReMCP updated. Run `remcp --version` or `remcp status` to verify.');
|
|
149
171
|
return;
|
|
@@ -152,8 +174,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
152
174
|
if (command === 'uninstall') {
|
|
153
175
|
uninstallLinuxService();
|
|
154
176
|
if (flags.purge) {
|
|
155
|
-
|
|
156
|
-
|
|
177
|
+
const cfg = loadConfig(false);
|
|
178
|
+
const specs = [PACKAGE_NAME, ...(cfg?.runtime?.packageName ? [cfg.runtime.packageName] : [])];
|
|
179
|
+
run(npmCommand, ['uninstall', '--global', ...specs, '--no-audit', '--no-fund', '--loglevel=error']);
|
|
157
180
|
}
|
|
158
181
|
return;
|
|
159
182
|
}
|
package/src/runtime.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
const PACKAGE_NAME = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/i;
|
|
4
|
+
const PACKAGE_SPEC = /^[^\s]+$/;
|
|
5
|
+
|
|
6
|
+
export function normalizeRuntime(value) {
|
|
7
|
+
if (!value || value.kind !== 'npm') throw new Error('Pairing server did not provide a supported local runtime');
|
|
8
|
+
const packageName = String(value.packageName || '');
|
|
9
|
+
const packageSpec = String(value.packageSpec || '');
|
|
10
|
+
const entry = String(value.entry || '');
|
|
11
|
+
if (!PACKAGE_NAME.test(packageName)) throw new Error('Pairing server returned an invalid runtime package name');
|
|
12
|
+
if (!PACKAGE_SPEC.test(packageSpec) || !packageSpec.startsWith(`${packageName}@`)) throw new Error('Pairing server returned an invalid runtime package spec');
|
|
13
|
+
if (!entry || path.isAbsolute(entry) || entry.split(/[\\/]+/).includes('..')) throw new Error('Pairing server returned an invalid runtime entry');
|
|
14
|
+
return { kind: 'npm', packageName, packageSpec, entry };
|
|
15
|
+
}
|
package/src/version.mjs
CHANGED
|
@@ -4,5 +4,3 @@ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url),
|
|
|
4
4
|
|
|
5
5
|
export const PACKAGE_NAME = pkg.name;
|
|
6
6
|
export const VERSION = pkg.version;
|
|
7
|
-
export const LOCAL_RUNTIME_NAME = '@wonderwhy-er/desktop-commander';
|
|
8
|
-
export const LOCAL_RUNTIME_SPEC = `${LOCAL_RUNTIME_NAME}@0.2.50`;
|
package/THIRD_PARTY_NOTICES.md
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# Third-party notices
|
|
2
|
-
|
|
3
|
-
ReMCP uses `@wonderwhy-er/desktop-commander` as its local device MCP backend. Desktop Commander is MIT licensed.
|
|
4
|
-
|
|
5
|
-
Copyright (c) 2024-2025 Eduard Ruzga and Desktop Commander Contributors.
|
|
6
|
-
|
|
7
|
-
The upstream project is available at https://github.com/wonderwhy-er/DesktopCommanderMCP. ReMCP does not use or depend on the proprietary Remote Desktop Commander cloud relay.
|
package/assets/remcp-icon.png
DELETED
|
Binary file
|
package/mcp.json
DELETED
package/plugin.json
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
|
-
"name": "remcp",
|
|
4
|
-
"version": "0.1.2",
|
|
5
|
-
"description": "Securely use files, directories, searches, terminal sessions, and processes on computers you have paired with ReMCP.",
|
|
6
|
-
"homepage": "https://remcp.delio24.com",
|
|
7
|
-
"repository": "https://github.com/antonbaider/remcp",
|
|
8
|
-
"license": "MIT",
|
|
9
|
-
"keywords": [
|
|
10
|
-
"mcp",
|
|
11
|
-
"remote-computer",
|
|
12
|
-
"developer-tools",
|
|
13
|
-
"files",
|
|
14
|
-
"terminal"
|
|
15
|
-
],
|
|
16
|
-
"extensions": {
|
|
17
|
-
"com.openai": {
|
|
18
|
-
"interface": {
|
|
19
|
-
"displayName": "ReMCP",
|
|
20
|
-
"shortDescription": "Use your paired computers through MCP.",
|
|
21
|
-
"longDescription": "ReMCP securely connects ChatGPT and Codex to computers you have explicitly paired. Inspect files and directories, search local content, and manage terminal sessions and processes through an authenticated remote MCP endpoint.",
|
|
22
|
-
"category": "Developer Tools",
|
|
23
|
-
"websiteURL": "https://remcp.delio24.com",
|
|
24
|
-
"privacyPolicyURL": "https://remcp.delio24.com/privacy",
|
|
25
|
-
"termsOfServiceURL": "https://remcp.delio24.com/terms",
|
|
26
|
-
"defaultPrompt": [
|
|
27
|
-
"Show my paired ReMCP computers and tell me which ones are online.",
|
|
28
|
-
"On my laptop, list the files in my project folder and summarize what is there without changing anything.",
|
|
29
|
-
"On my server, list the running processes and help me identify the one using the most memory."
|
|
30
|
-
],
|
|
31
|
-
"brandColor": "#4C50D8",
|
|
32
|
-
"composerIcon": "./assets/remcp-icon.png",
|
|
33
|
-
"logo": "./assets/remcp-icon.png"
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
Binary file
|
|
Binary file
|