@remcp/remcp 0.1.1 → 0.1.3

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,90 +1,116 @@
1
1
  # ReMCP
2
2
 
3
- ReMCP is an open-source remote MCP bridge for computers you control. A local agent connects outbound to a ReMCP server and exposes filesystem, search, terminal and process tools through one authenticated remote MCP endpoint.
3
+ ReMCP is an open-source remote MCP bridge for computers you control. A lightweight device agent connects **outbound** to a ReMCP relay, so ChatGPT, Codex, or another MCP client can work with local files, directories, searches, terminal sessions, and processes without exposing an inbound port on the computer.
4
4
 
5
- **Hosted service:** https://remcp.delio24.com
6
- **MCP endpoint:** `https://remcp.delio24.com/mcp`
5
+ - Hosted service: <https://remcp.delio24.com>
6
+ - MCP endpoint: `https://remcp.delio24.com/mcp`
7
+ - npm: `@remcp/remcp`
8
+ - License: MIT
7
9
 
8
- ## How it works
10
+ ## Quick start
9
11
 
10
- ```text
11
- ChatGPT / MCP client ── OAuth + HTTPS ──> ReMCP server
12
-
13
- ├── WSS ──> laptop agent ──> local MCP
14
- └── WSS ──> server agent ──> local MCP
15
- ```
16
-
17
- The central service stores account/device metadata, hashed reusable credentials and OAuth state. Actual filesystem and process access stays on the paired computer.
12
+ Requires Node.js 22.5 or newer.
18
13
 
19
- ## Install and pair
20
-
21
- Requires Node.js 22.5 or newer. Generate a one-time pairing code after signing in at https://remcp.delio24.com, then run:
14
+ 1. Install the CLI:
22
15
 
23
16
  ```bash
24
- npx --yes @remcp/remcp@latest connect --server https://remcp.delio24.com --code YOUR_PAIRING_CODE --install
17
+ npm install --global @remcp/remcp@latest
25
18
  ```
26
19
 
27
- `--install` creates a persistent global `remcp` CLI and Linux user service. The initial `npx` execution is only the bootstrap step.
20
+ 2. Open <https://remcp.delio24.com/#connect>, sign in with Google or GitHub, and choose **Generate pairing command**. Run the exact generated command on the computer you want to pair.
28
21
 
29
- Verify:
22
+ 3. Verify:
30
23
 
31
24
  ```bash
32
25
  remcp --version
33
26
  remcp status
34
27
  ```
35
28
 
29
+ 4. Add the MCP endpoint to your client:
30
+
31
+ ```text
32
+ https://remcp.delio24.com/mcp
33
+ ```
34
+
35
+ Authentication uses OAuth authorization code + PKCE with rotating refresh credentials.
36
+
36
37
  ## Update
37
38
 
38
39
  ```bash
39
40
  remcp update
40
41
  ```
41
42
 
42
- This updates the global CLI to the latest published release, refreshes the compatible local runtime, and restarts the installed user service.
43
+ `remcp update` refreshes the global CLI and compatible local runtime, then restarts the installed user service.
43
44
 
44
45
  ## CLI
45
46
 
46
47
  ```text
47
48
  remcp start Run the device agent in the foreground
48
- remcp status Check pairing, version and server health
49
+ remcp status Show version, pairing and server health
49
50
  remcp doctor Alias for status
50
- remcp update Update ReMCP and restart the installed service
51
+ remcp update Update ReMCP and restart the user service
51
52
  remcp install Install or repair the user service
52
53
  remcp uninstall Remove the user service
53
54
  remcp uninstall --purge Remove the service and global ReMCP packages
54
55
  remcp --version Print the installed CLI version
55
56
  ```
56
57
 
57
- ## Self-host the server
58
+ ## Architecture
58
59
 
59
- ```bash
60
- git clone https://github.com/antonbaider/remcp.git
61
- cd remcp
62
- cp .env.example .env
63
- # Fill in your public URL and Firebase Web App configuration.
64
- docker compose up -d --build
60
+ ```text
61
+ ChatGPT / Codex / MCP client
62
+
63
+ OAuth 2.1 + HTTPS
64
+
65
+ ReMCP relay /mcp
66
+
67
+ │ authenticated WSS (outbound from device)
68
+
69
+ paired computer
70
+
71
+ └── local file / search / terminal / process runtime
65
72
  ```
66
73
 
67
- Put a TLS reverse proxy in front of ReMCP and proxy WebSocket upgrades for `/agent`. The example Compose file binds the application to localhost by default. Do not publish the internal application port directly unless you have deliberately secured it.
74
+ The hosted relay stores account/device metadata, one-way hashes of reusable credentials, OAuth client metadata, and refresh-token hashes. Device credentials are independently revocable. MCP sessions are scoped to the authenticated ReMCP account.
75
+
76
+ ## OpenAI Plugin package
68
77
 
69
- ReMCP does **not** require a Firebase Admin service-account private key. Firebase ID tokens are verified using Google's public signing keys.
78
+ The repository is prepared for the current OpenAI Plugins format:
70
79
 
71
- ## ChatGPT / remote MCP
80
+ - `plugin.json` portable Agent Plugins manifest;
81
+ - `mcp.json` — production Streamable HTTP MCP configuration;
82
+ - `skills/remcp-operator/SKILL.md` — bundled operator skill;
83
+ - `assets/remcp-icon.png` — plugin icon;
84
+ - `docs/OPENAI_PLUGIN.md` — submission and reviewer checklist;
85
+ - `submission/openai-plugin-review.json` — generated tool annotations plus five positive and three negative review cases.
72
86
 
73
- ReMCP exposes Streamable HTTP MCP at `/mcp`. Authentication uses authorization code + PKCE, Dynamic Client Registration, short-lived access tokens and rotating refresh tokens.
87
+ ReMCP has no plugin UI, so the public submission should not include UI screenshots. The remote MCP server uses a universal production URL and OAuth. See [`docs/OPENAI_PLUGIN.md`](docs/OPENAI_PLUGIN.md) before submitting.
74
88
 
75
- The repository includes `chatgpt-app-submission.json` plus a reusable ReMCP operator skill for submission/testing workflows.
76
89
  ## Security model
77
90
 
78
- - one independent credential per paired device;
79
- - device and refresh credentials are stored as one-way hashes server-side;
91
+ - no inbound port is required on paired computers;
92
+ - one revocable credential is issued per paired device;
93
+ - reusable credentials are hashed server-side;
80
94
  - pairing codes are short-lived and single-use;
81
- - MCP sessions are bound to the authenticated account;
82
- - the device agent connects outbound over WSS;
83
- - write/process tools expose explicit MCP risk annotations;
84
- - the public review sandbox is isolated per authenticated user and intentionally limits terminal commands;
85
- - central Docker deployment runs read-only with dropped Linux capabilities in the supplied Compose configuration.
95
+ - OAuth uses PKCE and rotating refresh credentials;
96
+ - OAuth metadata advertises `openid` and `email`, with a UserInfo endpoint for verified-email workspace restrictions;
97
+ - MCP tools carry explicit `readOnlyHint`, `openWorldHint`, and `destructiveHint` annotations;
98
+ - the public review fixture is isolated per authenticated account and intentionally restricts terminal commands;
99
+ - the supplied server container runs read-only with dropped Linux capabilities.
86
100
 
87
- See https://remcp.delio24.com/security for the hosted-service security overview.
101
+ ReMCP tools should not be used to request or process passwords, MFA codes, API/private keys, payment-card data, protected health information, or government identifiers. See [`SECURITY.md`](SECURITY.md) and the hosted <https://remcp.delio24.com/security> page.
102
+
103
+ ## Self-hosting
104
+
105
+ ```bash
106
+ git clone https://github.com/antonbaider/remcp.git
107
+ cd remcp
108
+ cp .env.example .env
109
+ # Fill in your public URL and Firebase Web App configuration.
110
+ docker compose up -d --build
111
+ ```
112
+
113
+ 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.
88
114
 
89
115
  ## Development
90
116
 
@@ -93,8 +119,25 @@ npm ci
93
119
  npm run check
94
120
  npm test
95
121
  npm audit --omit=dev
122
+ npm pack --dry-run
123
+ ```
124
+
125
+ For server changes:
126
+
127
+ ```bash
128
+ docker build -t remcp:test .
96
129
  ```
97
130
 
131
+ See [`CONTRIBUTING.md`](CONTRIBUTING.md) for contribution and security rules.
132
+
133
+ ## Project docs
134
+
135
+ - [`docs/OPENAI_PLUGIN.md`](docs/OPENAI_PLUGIN.md) — OpenAI Plugins submission
136
+ - [`SECURITY.md`](SECURITY.md) — vulnerability reporting and operational security
137
+ - [`CONTRIBUTING.md`](CONTRIBUTING.md) — development checks
138
+ - [`CHANGELOG.md`](CHANGELOG.md) — release notes
139
+ - [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) — third-party license notices
140
+
98
141
  ## License
99
142
 
100
- ReMCP is MIT licensed. Third-party notices for the local device runtime are preserved in `THIRD_PARTY_NOTICES.md`. ReMCP is self-hostable and does not depend on a proprietary hosted relay.
143
+ ReMCP is MIT licensed. Third-party notices for the local device runtime are preserved in `THIRD_PARTY_NOTICES.md`.
Binary file
package/mcp.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
3
+ "mcpServers": {
4
+ "remcp": {
5
+ "type": "streamable-http",
6
+ "url": "https://remcp.delio24.com/mcp"
7
+ }
8
+ }
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/remcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Self-hosted remote MCP bridge for securely controlling your own computers from MCP clients.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -15,8 +15,10 @@
15
15
  "bin",
16
16
  "src",
17
17
  "public",
18
+ "plugin.json",
19
+ "mcp.json",
20
+ "assets",
18
21
  "skills/remcp-operator",
19
- "chatgpt-app-submission.json",
20
22
  "LICENSE",
21
23
  "THIRD_PARTY_NOTICES.md",
22
24
  "README.md"
package/plugin.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
+ "name": "remcp",
4
+ "version": "0.1.3",
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
+ }
package/public/app.js CHANGED
@@ -1,37 +1,37 @@
1
1
  import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.14.1/firebase-app.js';
2
- import { getAuth, GoogleAuthProvider, onAuthStateChanged, signInWithPopup, signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut } from 'https://www.gstatic.com/firebasejs/10.14.1/firebase-auth.js';
2
+ import { getAuth, GoogleAuthProvider, GithubAuthProvider, onAuthStateChanged, signInWithPopup, signOut } from 'https://www.gstatic.com/firebasejs/10.14.1/firebase-auth.js';
3
3
 
4
4
  const cfg = await fetch(`/api/config?fresh=${Date.now()}`, { cache: 'no-store' }).then(r => r.json());
5
5
  const auth = getAuth(initializeApp(cfg.firebase));
6
6
  const $ = selector => document.querySelector(selector);
7
- const showError = error => { $('#auth-error').textContent = error?.message || String(error); };
8
7
  const googleProvider = new GoogleAuthProvider();
8
+ const githubProvider = new GithubAuthProvider();
9
+ googleProvider.setCustomParameters({ prompt: 'select_account' });
9
10
 
10
- $('#mcp-url').textContent = cfg.mcpUrl;
11
- $('#google').onclick = async () => {
12
- const button = $('#google');
11
+ const friendlyAuthError = error => {
12
+ const code = error?.code || '';
13
+ if (code === 'auth/popup-closed-by-user' || code === 'auth/cancelled-popup-request') return '';
14
+ if (code === 'auth/account-exists-with-different-credential') return 'This email is already linked to another sign-in provider.';
15
+ if (code === 'auth/operation-not-allowed') return 'This sign-in provider is not enabled.';
16
+ return error?.message || String(error);
17
+ };
18
+ const showError = error => { $('#auth-error').textContent = friendlyAuthError(error); };
19
+
20
+ async function signIn(button, provider, idleLabel) {
13
21
  const label = button.querySelector('span');
14
22
  button.disabled = true;
15
23
  $('#auth-error').textContent = '';
16
- label.textContent = 'Opening Google…';
17
- try { await signInWithPopup(auth, googleProvider); }
24
+ label.textContent = 'Opening…';
25
+ try { await signInWithPopup(auth, provider); }
18
26
  catch (error) { showError(error); }
19
- finally { button.disabled = false; label.textContent = 'Continue with Google'; }
20
- };
21
- $('#email-login').onclick = () => signInWithEmailAndPassword(auth, $('#email').value, $('#password').value).catch(showError);
22
- $('#email-register').onclick = () => createUserWithEmailAndPassword(auth, $('#email').value, $('#password').value).catch(showError);
27
+ finally { button.disabled = false; label.textContent = idleLabel; }
28
+ }
29
+
30
+ $('#mcp-url').textContent = cfg.mcpUrl;
31
+ $('#google').onclick = () => signIn($('#google'), googleProvider, 'Continue with Google');
32
+ $('#github').onclick = () => signIn($('#github'), githubProvider, 'Continue with GitHub');
23
33
  $('#logout').onclick = () => signOut(auth);
24
34
 
25
- document.addEventListener('click', async event => {
26
- const button = event.target.closest('[data-copy]');
27
- if (!button) return;
28
- const target = document.querySelector(button.dataset.copy);
29
- if (!target) return;
30
- await navigator.clipboard.writeText(target.textContent.trim());
31
- const original = button.textContent;
32
- button.textContent = 'Copied';
33
- setTimeout(() => { button.textContent = original; }, 1200);
34
- });
35
35
  async function api(path, options = {}) {
36
36
  const token = await auth.currentUser.getIdToken();
37
37
  const response = await fetch(path, { ...options, headers: { ...(options.headers || {}), authorization: `Bearer ${token}`, 'content-type': 'application/json' } });
@@ -70,16 +70,17 @@ async function refreshDevices() {
70
70
  const data = await api('/api/me');
71
71
  const root = $('#devices');
72
72
  root.replaceChildren();
73
- if (!data.devices.length) { root.textContent = 'No devices yet.'; return; }
73
+ if (!data.devices.length) { root.textContent = 'No paired devices.'; return; }
74
74
  for (const device of data.devices) root.append(deviceRow(device));
75
75
  }
76
+
76
77
  $('#pair').onclick = async () => {
77
78
  try {
78
79
  const pairing = await api('/api/pair/create', { method: 'POST', body: '{}' });
79
80
  $('#pairing').classList.remove('hidden');
80
81
  $('#pair-command').textContent = pairing.command;
81
82
  const expiry = new Intl.DateTimeFormat(undefined, { hour: 'numeric', minute: '2-digit' }).format(new Date(pairing.expiresAt));
82
- $('#pair-expiry').textContent = `Expires ${expiry}`;
83
+ $('#pair-expiry').textContent = `One-time command · expires ${expiry}`;
83
84
  } catch (error) { showError(error); }
84
85
  };
85
86
 
@@ -90,6 +91,6 @@ onAuthStateChanged(auth, async user => {
90
91
  $('#dashboard').classList.toggle('hidden', !signedIn);
91
92
  $('#auth-error').textContent = '';
92
93
  if (!signedIn) return;
93
- $('#user-label').textContent = user.email || user.uid;
94
+ $('#user-label').textContent = user.email || user.displayName || user.uid;
94
95
  try { await refreshDevices(); } catch (error) { showError(error); }
95
96
  });
@@ -8,7 +8,7 @@
8
8
  <title>Authorize · ReMCP</title>
9
9
  <meta name="description" content="Authorize an MCP client to access computers paired to your ReMCP account.">
10
10
  <link rel="preconnect" href="https://www.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/style.css?v=remcp-012">
11
+ <link rel="stylesheet" href="/style.css?v=remcp-013">
12
12
  <meta property="og:site_name" content="ReMCP">
13
13
  <meta property="og:image" content="https://remcp.delio24.com/assets/og-remcp.png">
14
14
  <link rel="icon" href="/favicon.ico" sizes="any">
@@ -28,43 +28,32 @@
28
28
  <img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
29
29
  <span translate="no">ReMCP</span>
30
30
  </a>
31
- <nav aria-label="Primary"><a href="/docs">Docs</a><a href="/docs#install">Install</a><a href="/docs#update">Update</a><a href="/security">Security</a><a href="/support">Support</a></nav>
31
+ <nav aria-label="Primary"><a href="/docs">Docs</a><a href="/security">Security</a><a href="/support">Support</a></nav>
32
32
  </header>
33
33
  <main id="oauth-main" class="oauth-shell">
34
34
  <section class="oauth-card">
35
35
  <div id="oauth-login">
36
36
  <p class="eyebrow">Secure authorization</p>
37
37
  <h2 id="oauth-title">Sign in to ReMCP</h2>
38
- <p class="muted">Verify your account before reviewing this MCP client’s access request.</p>
39
- <button id="google" class="button google-button" type="button">
40
- <svg class="google-icon" viewBox="0 0 18 18" aria-hidden="true" focusable="false">
41
- <path fill="#4285F4" d="M17.64 9.205c0-.638-.057-1.252-.164-1.841H9v3.481h4.844a4.14 4.14 0 0 1-1.796 2.715v2.259h2.909c1.702-1.567 2.683-3.874 2.683-6.614Z"/>
42
- <path fill="#34A853" d="M9 18c2.43 0 4.468-.806 5.957-2.181l-2.909-2.259c-.806.54-1.835.859-3.048.859-2.344 0-4.328-1.585-5.037-3.714H.956v2.332A9 9 0 0 0 9 18Z"/>
43
- <path fill="#FBBC05" d="M3.963 10.705A5.41 5.41 0 0 1 3.682 9c0-.592.102-1.168.281-1.705V4.963H.956A9 9 0 0 0 0 9c0 1.452.347 2.827.956 4.037l3.007-2.332Z"/>
44
- <path fill="#EA4335" d="M9 3.581c1.321 0 2.507.454 3.441 1.346l2.581-2.581C13.464.892 11.426 0 9 0A9 9 0 0 0 .956 4.963l3.007 2.332C4.672 5.166 6.656 3.581 9 3.581Z"/>
45
- </svg>
46
- <span>Continue with Google</span>
47
- </button>
48
- <div class="divider"><span>or use email</span></div>
49
- <label for="email">Email</label>
50
- <input id="email" name="email" type="email" autocomplete="email" inputmode="email" autocapitalize="none" spellcheck="false">
51
- <label for="password">Password</label>
52
- <input id="password" name="password" type="password" autocomplete="current-password">
53
- <div class="button-row"><button id="email-login" class="button secondary" type="button">Sign In</button><button id="email-register" class="button ghost" type="button">Create Account</button></div>
38
+ <p class="muted">Continue with Google or GitHub, then review the MCP client’s access request.</p>
39
+ <div class="provider-stack">
40
+ <button id="google" class="provider-button google-button" type="button"><svg class="provider-icon" viewBox="0 0 18 18" aria-hidden="true"><path fill="#4285F4" d="M17.64 9.205c0-.638-.057-1.252-.164-1.841H9v3.481h4.844a4.14 4.14 0 0 1-1.796 2.715v2.259h2.909c1.702-1.567 2.683-3.874 2.683-6.614Z"/><path fill="#34A853" d="M9 18c2.43 0 4.468-.806 5.957-2.181l-2.909-2.259c-.806.54-1.835.859-3.048.859-2.344 0-4.328-1.585-5.037-3.714H.956v2.332A9 9 0 0 0 9 18Z"/><path fill="#FBBC05" d="M3.963 10.705A5.41 5.41 0 0 1 3.682 9c0-.592.102-1.168.281-1.705V4.963H.956A9 9 0 0 0 0 9c0 1.452.347 2.827.956 4.037l3.007-2.332Z"/><path fill="#EA4335" d="M9 3.581c1.321 0 2.507.454 3.441 1.346l2.581-2.581C13.464.892 11.426 0 9 0A9 9 0 0 0 .956 4.963l3.007 2.332C4.672 5.166 6.656 3.581 9 3.581Z"/></svg><span>Continue with Google</span></button>
41
+ <button id="github" class="provider-button github-button" type="button"><svg class="provider-icon" viewBox="0 0 24 24" aria-hidden="true"><path fill="currentColor" d="M12 .7a11.5 11.5 0 0 0-3.64 22.41c.58.11.79-.25.79-.56v-2.24c-3.22.7-3.9-1.37-3.9-1.37-.53-1.34-1.29-1.7-1.29-1.7-1.05-.72.08-.71.08-.71 1.16.08 1.77 1.19 1.77 1.19 1.04 1.77 2.72 1.26 3.38.96.1-.75.4-1.26.73-1.55-2.57-.29-5.27-1.29-5.27-5.68 0-1.26.45-2.29 1.19-3.1-.12-.29-.52-1.47.11-3.06 0 0 .97-.31 3.16 1.18a10.9 10.9 0 0 1 5.76 0c2.19-1.49 3.16-1.18 3.16-1.18.63 1.59.23 2.77.11 3.06.74.81 1.19 1.84 1.19 3.1 0 4.4-2.71 5.38-5.29 5.67.42.36.79 1.07.79 2.16v3.21c0 .31.21.68.8.56A11.5 11.5 0 0 0 12 .7Z"/></svg><span>Continue with GitHub</span></button>
42
+ </div>
54
43
  </div>
55
44
  <div id="oauth-consent" class="hidden">
56
45
  <p class="eyebrow">Access request</p>
57
46
  <h2>Authorize ReMCP Access</h2>
58
47
  <p class="muted">This MCP client is requesting access to operate computers paired to this ReMCP account.</p>
59
- <ul class="scope-list"><li><span>Control paired devices</span><strong>remcp:control</strong></li><li><span>Maintain the authorized connection</span><strong>offline_access</strong></li></ul>
48
+ <ul class="scope-list"><li><span>Identify your ReMCP account</span><strong>openid</strong></li><li><span>Share your verified email for workspace access rules</span><strong>email</strong></li><li><span>Control paired devices</span><strong>remcp:control</strong></li><li><span>Maintain the authorized connection</span><strong>offline_access</strong></li></ul>
60
49
  <p class="oauth-meta">Client: <span id="client-id"></span></p>
61
50
  <div class="oauth-actions"><button id="authorize" class="button" type="button">Authorize ReMCP Access</button><a class="button ghost" href="/">Not Now</a></div>
62
51
  </div>
63
52
  <p id="status" class="muted" aria-live="polite"></p>
64
53
  <p id="error" class="error" role="alert" aria-live="assertive"></p>
65
- <p class="footer-mini"><a href="/docs">Docs</a> · <a href="/docs#install">Install</a> · <a href="/docs#update">Update</a> · <a href="/security">Security</a> · <a href="/privacy">Privacy</a> · <a href="/terms">Terms</a></p>
54
+ <p class="footer-mini"><a href="/docs">Docs</a> · <a href="/security">Security</a> · <a href="/privacy">Privacy</a> · <a href="/terms">Terms</a></p>
66
55
  </section>
67
56
  </main>
68
- <script type="module" src="/oauth-app.js?v=36155fefb47e"></script>
57
+ <script type="module" src="/oauth-app.js?v=remcp-013"></script>
69
58
  </body>
70
59
  </html>
package/public/copy.js CHANGED
@@ -14,13 +14,7 @@ function legacyCopy(text) {
14
14
 
15
15
  async function copyText(text) {
16
16
  if (window.isSecureContext && navigator.clipboard?.writeText) {
17
- try {
18
- await navigator.clipboard.writeText(text);
19
- return;
20
- } catch {
21
- legacyCopy(text);
22
- return;
23
- }
17
+ try { await navigator.clipboard.writeText(text); return; } catch {}
24
18
  }
25
19
  legacyCopy(text);
26
20
  }
@@ -30,20 +24,17 @@ document.addEventListener('click', async event => {
30
24
  if (!button) return;
31
25
  const target = document.querySelector(button.dataset.copy);
32
26
  if (!target) return;
33
-
34
- const original = button.textContent;
35
- const originalLabel = button.getAttribute('aria-label') || 'Copy command';
27
+ const text = ('value' in target ? target.value : target.textContent).trim();
28
+ const label = button.getAttribute('aria-label') || 'Copy';
36
29
  try {
37
- await copyText(target.textContent.trim());
38
- button.textContent = 'Copied';
39
- button.setAttribute('aria-label', 'Copied to clipboard');
30
+ await copyText(text);
31
+ button.dataset.copied = 'true';
32
+ button.setAttribute('aria-label', 'Copied');
40
33
  } catch {
41
- button.textContent = 'Copy failed';
42
- button.setAttribute('aria-label', 'Copy failed. Select the command manually.');
34
+ button.setAttribute('aria-label', 'Copy failed');
43
35
  }
44
-
45
36
  window.setTimeout(() => {
46
- button.textContent = original;
47
- button.setAttribute('aria-label', originalLabel);
37
+ delete button.dataset.copied;
38
+ button.setAttribute('aria-label', label);
48
39
  }, 1400);
49
40
  });
package/public/docs.html CHANGED
@@ -8,7 +8,7 @@
8
8
  <title>Docs · ReMCP</title>
9
9
  <meta name="description" content="Connect a computer to ReMCP and configure the hosted MCP endpoint.">
10
10
  <link rel="preconnect" href="https://www.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/style.css?v=remcp-012">
11
+ <link rel="stylesheet" href="/style.css?v=remcp-013">
12
12
  <meta property="og:site_name" content="ReMCP">
13
13
  <meta property="og:image" content="https://remcp.delio24.com/assets/og-remcp.png">
14
14
  <link rel="icon" href="/favicon.ico" sizes="any">
@@ -28,57 +28,56 @@
28
28
  <img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
29
29
  <span translate="no">ReMCP</span>
30
30
  </a>
31
- <nav aria-label="Primary"><a href="/docs" aria-current="page">Docs</a><a href="/docs#install">Install</a><a href="/docs#update">Update</a><a href="/security">Security</a><a href="/support">Support</a></nav>
31
+ <nav aria-label="Primary"><a href="/docs" aria-current="page">Docs</a><a href="/security">Security</a><a href="/support">Support</a></nav>
32
32
  </header>
33
33
  <main id="main" class="doc-shell">
34
34
  <aside class="doc-nav" aria-label="Documentation"><a href="/docs" aria-current="page">Setup</a><a href="/docs#install">Install</a><a href="/docs#update">Update</a><a href="/security">Security</a><a href="/support">Support</a><a href="/privacy">Privacy</a><a href="/terms">Terms</a></aside>
35
35
  <article class="prose">
36
36
  <p class="eyebrow">Documentation</p>
37
- <h1>Install, pair and keep ReMCP updated.</h1>
38
- <p>Use <code>npx</code> once to pair a computer. With <code>--install</code>, ReMCP creates a persistent global CLI and a user service, so normal operation no longer depends on the temporary npx cache.</p>
37
+ <h1>Install and pair ReMCP.</h1>
38
+ <p>Install the CLI first. Pairing happens from the signed-in workspace, which generates the real one-time command for your computer.</p>
39
39
 
40
- <h2 id="requirements">1. Requirements</h2>
41
- <p>Install Node.js 22.5 or newer. The computer needs outbound HTTPS/WSS access to <code>remcp.delio24.com</code>; no inbound device port is required.</p>
40
+ <h2 id="requirements">Requirements</h2>
41
+ <p>Node.js 22.5 or newer. The computer needs outbound HTTPS/WSS access to <code>remcp.delio24.com</code>. No inbound device port is required.</p>
42
42
 
43
- <h2 id="pair">2. Get a pairing code</h2>
44
- <p>Open <a href="/#connect">the ReMCP workspace</a>, sign in, and choose <strong>Generate Command</strong>. Pairing codes are short-lived and single-use.</p>
43
+ <h2 id="install">1. Install the CLI</h2>
44
+ <div class="copy-field"><code id="docs-install">npm install --global @remcp/remcp@latest</code><button class="copy-icon-button" type="button" data-copy="#docs-install" aria-label="Copy install command"><svg class="icon-copy" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3"/></svg><svg class="icon-check" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 4 4L19 6"/></svg></button></div>
45
+ <p>This installs the <code>remcp</code> command. It does not pair a computer and does not contain a placeholder code.</p>
45
46
 
46
- <h2 id="install">3. Install and pair</h2>
47
- <div class="code-row"><pre><code id="docs-command-1">npx --yes @remcp/remcp@latest connect --server https://remcp.delio24.com --code YOUR_CODE --install</code></pre><button class="copy-button code-copy" type="button" data-copy="#docs-command-1" aria-label="Copy command" aria-live="polite">Copy</button></div>
48
- <p>This stores a device-specific credential locally, installs the persistent <code>remcp</code> CLI, installs the compatible local device runtime, and starts the ReMCP user service.</p>
47
+ <h2 id="pair">2. Pair a computer</h2>
48
+ <p>Open the <a href="/#connect">ReMCP workspace</a>, continue with Google or GitHub, and choose <strong>Generate pairing command</strong>. Run that exact generated command on the computer you want to pair.</p>
49
+ <div class="notice"><strong>Use the generated command.</strong><p>Pairing codes are short-lived and single-use. Do not type a made-up code or reuse an expired command.</p></div>
49
50
 
50
- <h3>Verify the installation</h3>
51
- <div class="code-row"><pre><code id="docs-command-2">remcp --version
52
- remcp status</code></pre><button class="copy-button code-copy" type="button" data-copy="#docs-command-2" aria-label="Copy command" aria-live="polite">Copy</button></div>
53
- <p><code>status</code> shows the installed CLI version, paired device id, server URL and server health.</p>
51
+ <h2 id="verify">3. Verify</h2>
52
+ <div class="copy-field multiline"><pre><code id="docs-verify">remcp --version
53
+ remcp status</code></pre><button class="copy-icon-button" type="button" data-copy="#docs-verify" aria-label="Copy verification commands"><svg class="icon-copy" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3"/></svg><svg class="icon-check" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 4 4L19 6"/></svg></button></div>
54
+ <p><code>status</code> shows the installed CLI version, pairing state and server health.</p>
54
55
 
55
56
  <h2 id="mcp">4. Add the MCP endpoint</h2>
56
- <div class="code-row"><pre><code id="docs-command-3">https://remcp.delio24.com/mcp</code></pre><button class="copy-button code-copy" type="button" data-copy="#docs-command-3" aria-label="Copy MCP endpoint" aria-live="polite">Copy</button></div>
57
- <p>Choose OAuth when your MCP client asks for authentication. ReMCP uses authorization code flow with PKCE and refresh tokens.</p>
57
+ <div class="copy-field"><code id="docs-mcp">https://remcp.delio24.com/mcp</code><button class="copy-icon-button" type="button" data-copy="#docs-mcp" aria-label="Copy MCP endpoint"><svg class="icon-copy" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3"/></svg><svg class="icon-check" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 4 4L19 6"/></svg></button></div>
58
+ <p>Add this endpoint to your MCP client and use OAuth when prompted. ReMCP uses authorization code flow with PKCE and refresh-token rotation.</p>
58
59
 
59
- <h2 id="update">Update ReMCP</h2>
60
- <div class="code-row"><pre><code id="docs-command-4">remcp update</code></pre><button class="copy-button code-copy" type="button" data-copy="#docs-command-4" aria-label="Copy command" aria-live="polite">Copy</button></div>
61
- <p>The update command installs the latest published ReMCP CLI, refreshes the compatible local runtime, and restarts the user service when it is installed. Verify afterwards with <code>remcp --version</code> or <code>remcp status</code>.</p>
60
+ <h2 id="update">Update</h2>
61
+ <div class="copy-field"><code id="docs-update">remcp update</code><button class="copy-icon-button" type="button" data-copy="#docs-update" aria-label="Copy update command"><svg class="icon-copy" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3"/></svg><svg class="icon-check" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 4 4L19 6"/></svg></button></div>
62
+ <p>Updates the installed ReMCP CLI and compatible local runtime, then restarts the user service when present.</p>
62
63
 
63
64
  <h2 id="service">Service commands</h2>
64
- <div class="code-row"><pre><code id="docs-command-5">remcp status
65
+ <div class="copy-field multiline"><pre><code id="docs-service">remcp status
65
66
  remcp doctor
66
67
  remcp install
67
- remcp start</code></pre><button class="copy-button code-copy" type="button" data-copy="#docs-command-5" aria-label="Copy command" aria-live="polite">Copy</button></div>
68
- <p><code>start</code> runs the agent in the foreground. <code>install</code> repairs or reinstalls the persistent user service for an already paired computer.</p>
68
+ remcp start</code></pre><button class="copy-icon-button" type="button" data-copy="#docs-service" aria-label="Copy service commands"><svg class="icon-copy" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3"/></svg><svg class="icon-check" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 4 4L19 6"/></svg></button></div>
69
+ <p><code>install</code> installs or repairs the background user service for an already paired computer. <code>start</code> runs the agent in the foreground.</p>
69
70
 
70
71
  <h2 id="uninstall">Uninstall</h2>
71
- <div class="code-row"><pre><code id="docs-command-6">remcp uninstall</code></pre><button class="copy-button code-copy" type="button" data-copy="#docs-command-6" aria-label="Copy command" aria-live="polite">Copy</button></div>
72
- <p>This stops and removes the background user service but keeps the CLI and local pairing configuration.</p>
73
- <div class="code-row"><pre><code id="docs-command-7">remcp uninstall --purge</code></pre><button class="copy-button code-copy" type="button" data-copy="#docs-command-7" aria-label="Copy command" aria-live="polite">Copy</button></div>
74
- <p><code>--purge</code> also removes the globally installed ReMCP packages. The local pairing configuration remains in <code>~/.config/remcp</code> so it is not silently destroyed.</p>
72
+ <div class="copy-field"><code id="docs-uninstall">remcp uninstall</code><button class="copy-icon-button" type="button" data-copy="#docs-uninstall" aria-label="Copy uninstall command"><svg class="icon-copy" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3"/></svg><svg class="icon-check" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 4 4L19 6"/></svg></button></div>
73
+ <p>Removes the background service. To also remove globally installed ReMCP packages:</p>
74
+ <div class="copy-field"><code id="docs-purge">remcp uninstall --purge</code><button class="copy-icon-button" type="button" data-copy="#docs-purge" aria-label="Copy purge command"><svg class="icon-copy" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3"/></svg><svg class="icon-check" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 4 4L19 6"/></svg></button></div>
75
75
 
76
76
  <h2 id="connection-model">Connection model</h2>
77
- <p>The device agent creates an outbound secure WebSocket connection to the relay. You do not need to expose a listener on the computer.</p>
78
- <div class="notice"><strong>Review mutating actions.</strong><p>Some tools can write files or run processes. Review the action requested by your MCP client before approving it.</p></div>
77
+ <p>The device agent opens an outbound secure WebSocket connection to the relay. MCP clients connect to the HTTPS MCP endpoint through OAuth. Only devices paired to the signed-in account are exposed to that account.</p>
79
78
 
80
79
  <h2 id="self-hosting">Self-hosting</h2>
81
- <p>The central ReMCP service runs in Docker with persistent account, device and OAuth metadata. Device agents run natively so host access does not require a privileged container.</p>
80
+ <p>The server can be self-hosted with Docker. Keep it behind TLS, persist its data volume, and proxy WebSocket upgrades for the device-agent route. See the repository README for deployment variables.</p>
82
81
  </article>
83
82
  </main>
84
83
  <footer class="site-footer">
@@ -86,9 +85,9 @@ remcp start</code></pre><button class="copy-button code-copy" type="button" data
86
85
  <img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
87
86
  <span translate="no">ReMCP</span>
88
87
  </div>
89
- <nav aria-label="Footer"><a href="/docs">Docs</a><a href="/docs#install">Install</a><a href="/docs#update">Update</a><a href="/security">Security</a><a href="/support">Support</a><a href="/privacy">Privacy</a><a href="/terms">Terms</a></nav>
88
+ <nav aria-label="Footer"><a href="/docs">Docs</a><a href="/security">Security</a><a href="/support">Support</a><a href="/privacy">Privacy</a><a href="/terms">Terms</a></nav>
90
89
  <p>Open-source remote MCP infrastructure.</p>
91
90
  </footer>
92
- <script type="module" src="/copy.js?v=remcp-012"></script>
91
+ <script type="module" src="/copy.js?v=remcp-013"></script>
93
92
  </body>
94
93
  </html>