@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.
Files changed (56) hide show
  1. package/README.md +54 -107
  2. package/package.json +12 -52
  3. package/src/agent.mjs +19 -24
  4. package/src/cli.mjs +42 -19
  5. package/src/runtime.mjs +15 -0
  6. package/src/version.mjs +0 -2
  7. package/THIRD_PARTY_NOTICES.md +0 -7
  8. package/assets/remcp-icon.png +0 -0
  9. package/mcp.json +0 -9
  10. package/plugin.json +0 -37
  11. package/public/android-chrome-192x192.png +0 -0
  12. package/public/android-chrome-512x512.png +0 -0
  13. package/public/app.js +0 -95
  14. package/public/apple-touch-icon.png +0 -0
  15. package/public/assets/art/hero-relay.webp +0 -0
  16. package/public/assets/art/relay-detail.webp +0 -0
  17. package/public/assets/icon-120.png +0 -0
  18. package/public/assets/icon-16.png +0 -0
  19. package/public/assets/icon-180.png +0 -0
  20. package/public/assets/icon-192.png +0 -0
  21. package/public/assets/icon-256.png +0 -0
  22. package/public/assets/icon-32.png +0 -0
  23. package/public/assets/icon-48.png +0 -0
  24. package/public/assets/icon-512.png +0 -0
  25. package/public/assets/og-remcp.png +0 -0
  26. package/public/authorize.html +0 -70
  27. package/public/copy.js +0 -49
  28. package/public/docs.html +0 -94
  29. package/public/favicon-16x16.png +0 -0
  30. package/public/favicon-32x32.png +0 -0
  31. package/public/favicon-48x48.png +0 -0
  32. package/public/favicon.ico +0 -0
  33. package/public/index.html +0 -154
  34. package/public/oauth-app.js +0 -54
  35. package/public/privacy.html +0 -60
  36. package/public/remcp-darkmode-16x16.png +0 -0
  37. package/public/remcp-darkmode-192x192.png +0 -0
  38. package/public/remcp-darkmode-32x32.png +0 -0
  39. package/public/remcp-darkmode-48x48.png +0 -0
  40. package/public/remcp-darkmode-512x512.png +0 -0
  41. package/public/security.html +0 -57
  42. package/public/site.webmanifest +0 -23
  43. package/public/style.css +0 -259
  44. package/public/support.html +0 -59
  45. package/public/terms.html +0 -60
  46. package/skills/remcp-operator/SKILL.md +0 -36
  47. package/src/auth.mjs +0 -71
  48. package/src/config.mjs +0 -51
  49. package/src/db.mjs +0 -102
  50. package/src/mcp.mjs +0 -95
  51. package/src/oauth.mjs +0 -164
  52. package/src/relay.mjs +0 -97
  53. package/src/review-sandbox.mjs +0 -96
  54. package/src/server.mjs +0 -159
  55. package/src/tool-catalog.json +0 -796
  56. package/src/util.mjs +0 -24
package/public/app.js DELETED
@@ -1,95 +0,0 @@
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';
3
-
4
- const cfg = await fetch(`/api/config?fresh=${Date.now()}`, { cache: 'no-store' }).then(r => r.json());
5
- const auth = getAuth(initializeApp(cfg.firebase));
6
- const $ = selector => document.querySelector(selector);
7
- const showError = error => { $('#auth-error').textContent = error?.message || String(error); };
8
- const googleProvider = new GoogleAuthProvider();
9
-
10
- $('#mcp-url').textContent = cfg.mcpUrl;
11
- $('#google').onclick = async () => {
12
- const button = $('#google');
13
- const label = button.querySelector('span');
14
- button.disabled = true;
15
- $('#auth-error').textContent = '';
16
- label.textContent = 'Opening Google…';
17
- try { await signInWithPopup(auth, googleProvider); }
18
- 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);
23
- $('#logout').onclick = () => signOut(auth);
24
-
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
- async function api(path, options = {}) {
36
- const token = await auth.currentUser.getIdToken();
37
- const response = await fetch(path, { ...options, headers: { ...(options.headers || {}), authorization: `Bearer ${token}`, 'content-type': 'application/json' } });
38
- if (!response.ok) throw new Error(await response.text());
39
- return response.status === 204 ? null : response.json();
40
- }
41
-
42
- function deviceRow(device) {
43
- const row = document.createElement('div');
44
- row.className = 'device';
45
- const details = document.createElement('div');
46
- const name = document.createElement('strong');
47
- name.textContent = device.name || 'Unnamed device';
48
- const meta = document.createElement('div');
49
- meta.className = 'muted';
50
- meta.textContent = `${device.platform || 'unknown'} · ${device.hostname || device.id}`;
51
- const status = document.createElement('div');
52
- status.className = device.online ? 'online' : 'offline';
53
- status.textContent = device.online ? 'Online' : 'Offline';
54
- details.append(name, meta, status);
55
- const revoke = document.createElement('button');
56
- revoke.className = 'danger';
57
- revoke.type = 'button';
58
- revoke.textContent = 'Revoke';
59
- revoke.setAttribute('aria-label', `Revoke ${device.name || 'device'}`);
60
- revoke.onclick = async () => {
61
- if (!confirm(`Revoke ${device.name || 'this device'}? It will need to be paired again before reconnecting.`)) return;
62
- await api(`/api/devices/${encodeURIComponent(device.id)}`, { method: 'DELETE' });
63
- await refreshDevices();
64
- };
65
- row.append(details, revoke);
66
- return row;
67
- }
68
-
69
- async function refreshDevices() {
70
- const data = await api('/api/me');
71
- const root = $('#devices');
72
- root.replaceChildren();
73
- if (!data.devices.length) { root.textContent = 'No devices yet.'; return; }
74
- for (const device of data.devices) root.append(deviceRow(device));
75
- }
76
- $('#pair').onclick = async () => {
77
- try {
78
- const pairing = await api('/api/pair/create', { method: 'POST', body: '{}' });
79
- $('#pairing').classList.remove('hidden');
80
- $('#pair-command').textContent = pairing.command;
81
- const expiry = new Intl.DateTimeFormat(undefined, { hour: 'numeric', minute: '2-digit' }).format(new Date(pairing.expiresAt));
82
- $('#pair-expiry').textContent = `Expires ${expiry}`;
83
- } catch (error) { showError(error); }
84
- };
85
-
86
- onAuthStateChanged(auth, async user => {
87
- const signedIn = Boolean(user);
88
- $('#signed-out').classList.toggle('hidden', signedIn);
89
- $('#signed-in').classList.toggle('hidden', !signedIn);
90
- $('#dashboard').classList.toggle('hidden', !signedIn);
91
- $('#auth-error').textContent = '';
92
- if (!signedIn) return;
93
- $('#user-label').textContent = user.email || user.uid;
94
- try { await refreshDevices(); } catch (error) { showError(error); }
95
- });
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,70 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <meta name="color-scheme" content="light">
7
- <meta name="theme-color" content="#f7f7f5">
8
- <title>Authorize · ReMCP</title>
9
- <meta name="description" content="Authorize an MCP client to access computers paired to your ReMCP account.">
10
- <link rel="preconnect" href="https://www.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/style.css?v=remcp-012">
12
- <meta property="og:site_name" content="ReMCP">
13
- <meta property="og:image" content="https://remcp.delio24.com/assets/og-remcp.png">
14
- <link rel="icon" href="/favicon.ico" sizes="any">
15
- <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" media="(prefers-color-scheme: light)">
16
- <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" media="(prefers-color-scheme: light)">
17
- <link rel="icon" type="image/png" sizes="48x48" href="/favicon-48x48.png" media="(prefers-color-scheme: light)">
18
- <link rel="icon" type="image/png" sizes="16x16" href="/remcp-darkmode-16x16.png" media="(prefers-color-scheme: dark)">
19
- <link rel="icon" type="image/png" sizes="32x32" href="/remcp-darkmode-32x32.png" media="(prefers-color-scheme: dark)">
20
- <link rel="icon" type="image/png" sizes="48x48" href="/remcp-darkmode-48x48.png" media="(prefers-color-scheme: dark)">
21
- <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
22
- <link rel="manifest" href="/site.webmanifest">
23
- </head>
24
- <body>
25
- <a class="skip-link" href="#oauth-main">Skip to authorization</a>
26
- <header class="site-header">
27
- <a class="wordmark" href="/" aria-label="ReMCP home">
28
- <img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
29
- <span translate="no">ReMCP</span>
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>
32
- </header>
33
- <main id="oauth-main" class="oauth-shell">
34
- <section class="oauth-card">
35
- <div id="oauth-login">
36
- <p class="eyebrow">Secure authorization</p>
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>
54
- </div>
55
- <div id="oauth-consent" class="hidden">
56
- <p class="eyebrow">Access request</p>
57
- <h2>Authorize ReMCP Access</h2>
58
- <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>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
- <p class="oauth-meta">Client: <span id="client-id"></span></p>
61
- <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
- </div>
63
- <p id="status" class="muted" aria-live="polite"></p>
64
- <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>
66
- </section>
67
- </main>
68
- <script type="module" src="/oauth-app.js?v=36155fefb47e"></script>
69
- </body>
70
- </html>
package/public/copy.js DELETED
@@ -1,49 +0,0 @@
1
- function legacyCopy(text) {
2
- const input = document.createElement('textarea');
3
- input.value = text;
4
- input.setAttribute('readonly', '');
5
- input.style.position = 'fixed';
6
- input.style.inset = '0 auto auto -9999px';
7
- document.body.append(input);
8
- input.select();
9
- input.setSelectionRange(0, input.value.length);
10
- const copied = document.execCommand('copy');
11
- input.remove();
12
- if (!copied) throw new Error('Clipboard write failed');
13
- }
14
-
15
- async function copyText(text) {
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
- }
24
- }
25
- legacyCopy(text);
26
- }
27
-
28
- document.addEventListener('click', async event => {
29
- const button = event.target.closest('[data-copy]');
30
- if (!button) return;
31
- const target = document.querySelector(button.dataset.copy);
32
- if (!target) return;
33
-
34
- const original = button.textContent;
35
- const originalLabel = button.getAttribute('aria-label') || 'Copy command';
36
- try {
37
- await copyText(target.textContent.trim());
38
- button.textContent = 'Copied';
39
- button.setAttribute('aria-label', 'Copied to clipboard');
40
- } catch {
41
- button.textContent = 'Copy failed';
42
- button.setAttribute('aria-label', 'Copy failed. Select the command manually.');
43
- }
44
-
45
- window.setTimeout(() => {
46
- button.textContent = original;
47
- button.setAttribute('aria-label', originalLabel);
48
- }, 1400);
49
- });
package/public/docs.html DELETED
@@ -1,94 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <meta name="color-scheme" content="light">
7
- <meta name="theme-color" content="#f7f7f5">
8
- <title>Docs · ReMCP</title>
9
- <meta name="description" content="Connect a computer to ReMCP and configure the hosted MCP endpoint.">
10
- <link rel="preconnect" href="https://www.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/style.css?v=remcp-012">
12
- <meta property="og:site_name" content="ReMCP">
13
- <meta property="og:image" content="https://remcp.delio24.com/assets/og-remcp.png">
14
- <link rel="icon" href="/favicon.ico" sizes="any">
15
- <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" media="(prefers-color-scheme: light)">
16
- <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" media="(prefers-color-scheme: light)">
17
- <link rel="icon" type="image/png" sizes="48x48" href="/favicon-48x48.png" media="(prefers-color-scheme: light)">
18
- <link rel="icon" type="image/png" sizes="16x16" href="/remcp-darkmode-16x16.png" media="(prefers-color-scheme: dark)">
19
- <link rel="icon" type="image/png" sizes="32x32" href="/remcp-darkmode-32x32.png" media="(prefers-color-scheme: dark)">
20
- <link rel="icon" type="image/png" sizes="48x48" href="/remcp-darkmode-48x48.png" media="(prefers-color-scheme: dark)">
21
- <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
22
- <link rel="manifest" href="/site.webmanifest">
23
- </head>
24
- <body>
25
- <a class="skip-link" href="#main">Skip to content</a>
26
- <header class="site-header">
27
- <a class="wordmark" href="/" aria-label="ReMCP home">
28
- <img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
29
- <span translate="no">ReMCP</span>
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>
32
- </header>
33
- <main id="main" class="doc-shell">
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
- <article class="prose">
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>
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>
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>
45
-
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>
49
-
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>
54
-
55
- <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>
58
-
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>
62
-
63
- <h2 id="service">Service commands</h2>
64
- <div class="code-row"><pre><code id="docs-command-5">remcp status
65
- remcp doctor
66
- 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>
69
-
70
- <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>
75
-
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>
79
-
80
- <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>
82
- </article>
83
- </main>
84
- <footer class="site-footer">
85
- <div class="wordmark">
86
- <img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
87
- <span translate="no">ReMCP</span>
88
- </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>
90
- <p>Open-source remote MCP infrastructure.</p>
91
- </footer>
92
- <script type="module" src="/copy.js?v=remcp-012"></script>
93
- </body>
94
- </html>
Binary file
Binary file
Binary file
Binary file
package/public/index.html DELETED
@@ -1,154 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width,initial-scale=1">
6
- <meta name="color-scheme" content="light">
7
- <meta name="theme-color" content="#f7f7f5">
8
- <title>ReMCP — Your computers over MCP</title>
9
- <meta name="description" content="Securely pair your own computers and use their files, terminal and processes through authenticated MCP clients.">
10
- <link rel="preconnect" href="https://www.gstatic.com" crossorigin>
11
- <link rel="stylesheet" href="/style.css?v=remcp-012">
12
- <meta property="og:site_name" content="ReMCP">
13
- <meta property="og:image" content="https://remcp.delio24.com/assets/og-remcp.png">
14
- <link rel="icon" href="/favicon.ico" sizes="any">
15
- <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" media="(prefers-color-scheme: light)">
16
- <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" media="(prefers-color-scheme: light)">
17
- <link rel="icon" type="image/png" sizes="48x48" href="/favicon-48x48.png" media="(prefers-color-scheme: light)">
18
- <link rel="icon" type="image/png" sizes="16x16" href="/remcp-darkmode-16x16.png" media="(prefers-color-scheme: dark)">
19
- <link rel="icon" type="image/png" sizes="32x32" href="/remcp-darkmode-32x32.png" media="(prefers-color-scheme: dark)">
20
- <link rel="icon" type="image/png" sizes="48x48" href="/remcp-darkmode-48x48.png" media="(prefers-color-scheme: dark)">
21
- <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
22
- <link rel="manifest" href="/site.webmanifest">
23
- </head>
24
- <body>
25
- <a class="skip-link" href="#main">Skip to content</a>
26
- <header class="site-header">
27
- <a class="wordmark" href="/" aria-label="ReMCP home">
28
- <img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
29
- <span translate="no">ReMCP</span>
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>
32
- </header>
33
- <main id="main">
34
- <section class="hero">
35
- <div class="hero-copy">
36
- <p class="eyebrow">Remote MCP, without exposed device ports</p>
37
- <h1>Your computers.<br>Available to MCP.</h1>
38
- <p class="lede">Pair a device once. ReMCP routes authenticated tool calls to its local agent while the device keeps an outbound-only connection.</p>
39
- <div class="hero-actions">
40
- <a class="button" href="#connect">Connect a Device</a>
41
- <a class="text-link" href="/docs">Read Setup Guide <span aria-hidden="true">→</span></a>
42
- </div>
43
- </div>
44
- <div class="hero-visual" role="img" aria-label="ReMCP connection diagram">
45
- <div class="node client"><span>ChatGPT / MCP client</span><b>OAuth 2.1</b></div>
46
- <div class="rail" aria-hidden="true"><span></span><i>encrypted</i></div>
47
- <div class="node relay"><span>ReMCP relay</span><b translate="no">remcp.delio24.com</b></div>
48
- <div class="rail" aria-hidden="true"><span></span><i>outbound</i></div>
49
- <div class="node device-node"><span>Your device</span><b>local agent</b><em class="pulse">online</em></div>
50
- </div>
51
- </section>
52
-
53
- <section class="principles" aria-label="Product principles">
54
- <div><span>01</span><h2>Private by default</h2><p>Accounts and devices are isolated by authenticated user identity and revocable device credentials.</p></div>
55
- <div><span>02</span><h2>No inbound laptop port</h2><p>The device agent connects outward to ReMCP. Your laptop does not need a public IP or open listener.</p></div>
56
- <div><span>03</span><h2>Explicit control</h2><p>Read, write and destructive tools declare their behavior so MCP clients can apply the right review flow.</p></div>
57
- </section>
58
-
59
- <section id="install" class="quickstart">
60
- <div class="section-heading">
61
- <p class="eyebrow">Quick start</p>
62
- <h2>One command to pair. One command to update.</h2>
63
- <p>Use npx only for the first pairing. <code>--install</code> creates a persistent <code>remcp</code> CLI and background user service.</p>
64
- </div>
65
- <div class="quickstart-grid">
66
- <article class="command-card">
67
- <span>01</span><h3>Install &amp; pair</h3>
68
- <div class="command-row"><code id="install-command">npx --yes @remcp/remcp@latest connect --server https://remcp.delio24.com --code YOUR_CODE --install</code><button class="copy-button" type="button" data-copy="#install-command">Copy</button></div>
69
- <p>Generate <code>YOUR_CODE</code> in the workspace below.</p>
70
- </article>
71
- <article class="command-card">
72
- <span>02</span><h3>Check</h3>
73
- <div class="command-row"><code id="status-command">remcp status</code><button class="copy-button" type="button" data-copy="#status-command">Copy</button></div>
74
- <p>Confirm the local CLI version, pairing and server health.</p>
75
- </article>
76
- <article id="update" class="command-card">
77
- <span>03</span><h3>Update</h3>
78
- <div class="command-row"><code id="update-command">remcp update</code><button class="copy-button" type="button" data-copy="#update-command">Copy</button></div>
79
- <p>Updates the CLI and restarts the installed user service.</p>
80
- </article>
81
- </div>
82
- <a class="text-link" href="/docs">Full installation and CLI guide <span aria-hidden="true">→</span></a>
83
- </section>
84
-
85
- <section id="connect" class="workspace">
86
- <div class="section-heading">
87
- <p class="eyebrow">Workspace</p>
88
- <h2>Pair and manage devices</h2>
89
- <p>Sign in to generate one-time pairing commands and revoke computers you no longer trust.</p>
90
- </div>
91
- <div class="workspace-grid">
92
- <section class="auth-pane" aria-labelledby="account-title">
93
- <h3 id="account-title">Account</h3>
94
- <div id="signed-out" class="stack">
95
- <p class="muted">Sign in to access your ReMCP device workspace.</p>
96
- <button id="google" class="button google-button" type="button">
97
- <svg class="google-icon" viewBox="0 0 18 18" aria-hidden="true" focusable="false">
98
- <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"/>
99
- <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"/>
100
- <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"/>
101
- <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"/>
102
- </svg>
103
- <span>Continue with Google</span>
104
- </button>
105
- <div class="divider"><span>or use email</span></div>
106
- <label for="email">Email</label>
107
- <input id="email" name="email" type="email" autocomplete="email" inputmode="email" autocapitalize="none" spellcheck="false">
108
- <label for="password">Password</label>
109
- <input id="password" name="password" type="password" autocomplete="current-password">
110
- <div class="button-row">
111
- <button id="email-login" class="button secondary" type="button">Sign In</button>
112
- <button id="email-register" class="button ghost" type="button">Create Account</button>
113
- </div>
114
- <p id="auth-error" class="error" role="alert" aria-live="polite"></p>
115
- </div>
116
- <div id="signed-in" class="hidden stack">
117
- <div class="identity"><span class="status-dot" aria-hidden="true"></span><div><strong id="user-label"></strong><small>Authenticated</small></div></div>
118
- <button id="logout" class="button ghost" type="button">Sign Out</button>
119
- </div>
120
- </section>
121
- <section class="endpoint-pane" aria-labelledby="endpoint-title">
122
- <div><h3 id="endpoint-title">MCP endpoint</h3><p class="muted">Use this HTTPS endpoint when adding ReMCP to an MCP client.</p></div>
123
- <div class="command-row"><code id="mcp-url">https://remcp.delio24.com/mcp</code><button class="copy-button" type="button" data-copy="#mcp-url" aria-label="Copy MCP endpoint">Copy</button></div>
124
- <dl class="endpoint-facts"><div><dt>Transport</dt><dd>Streamable HTTP</dd></div><div><dt>Authentication</dt><dd>OAuth 2.1 + PKCE</dd></div></dl>
125
- </section>
126
- </div>
127
- <section id="dashboard" class="hidden device-workspace" aria-label="Device management">
128
- <div class="pair-panel">
129
- <div><p class="eyebrow">Add a computer</p><h3>Generate a one-time pairing command</h3><p class="muted">The code expires automatically and can only be claimed once.</p></div>
130
- <button id="pair" class="button" type="button">Generate Command</button>
131
- <div id="pairing" class="hidden pairing-output"><code id="pair-command"></code><button class="copy-button" type="button" data-copy="#pair-command">Copy</button><p id="pair-expiry" class="muted"></p></div>
132
- </div>
133
- <div class="devices-panel"><div class="panel-title"><h3>Paired devices</h3><span class="muted">Online state updates when the page refreshes.</span></div><div id="devices" class="device-list" aria-live="polite">No devices yet.</div></div>
134
- </section>
135
- </section>
136
-
137
- <section class="final-cta">
138
- <p class="eyebrow">Self-hostable architecture</p>
139
- <h2>Keep the relay yours.</h2>
140
- <p>ReMCP can run as a small Docker service while each computer runs its own local agent.</p>
141
- <a class="text-link" href="/security">Review the Security Model <span aria-hidden="true">→</span></a>
142
- </section>
143
- </main>
144
- <footer class="site-footer">
145
- <div class="wordmark">
146
- <img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
147
- <span translate="no">ReMCP</span>
148
- </div>
149
- <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>
150
- <p>Open-source remote MCP infrastructure.</p>
151
- </footer>
152
- <script type="module" src="/app.js?v=14a728f07dd1"></script>
153
- </body>
154
- </html>
@@ -1,54 +0,0 @@
1
- import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.14.1/firebase-app.js';
2
- import { getAuth, GoogleAuthProvider, onAuthStateChanged, signInWithPopup, signInWithEmailAndPassword, createUserWithEmailAndPassword } from 'https://www.gstatic.com/firebasejs/10.14.1/firebase-auth.js';
3
-
4
- const cfg = await fetch(`/api/config?fresh=${Date.now()}`, { cache: 'no-store' }).then(r => r.json());
5
- const auth = getAuth(initializeApp(cfg.firebase));
6
- const query = new URLSearchParams(location.search);
7
- const $ = selector => document.querySelector(selector);
8
- const showError = error => { $('#error').textContent = error?.message || String(error); };
9
- let currentUser = null;
10
- let completing = false;
11
- const googleProvider = new GoogleAuthProvider();
12
-
13
- $('#client-id').textContent = query.get('client_id') || 'MCP client';
14
- $('#google').onclick = async () => {
15
- const button = $('#google');
16
- const label = button.querySelector('span');
17
- button.disabled = true;
18
- $('#error').textContent = '';
19
- label.textContent = 'Opening Google…';
20
- try { await signInWithPopup(auth, googleProvider); }
21
- catch (error) { showError(error); }
22
- finally { button.disabled = false; label.textContent = 'Continue with Google'; }
23
- };
24
- $('#email-login').onclick = () => signInWithEmailAndPassword(auth, $('#email').value, $('#password').value).catch(showError);
25
- $('#email-register').onclick = () => createUserWithEmailAndPassword(auth, $('#email').value, $('#password').value).catch(showError);
26
-
27
- onAuthStateChanged(auth, user => {
28
- currentUser = user;
29
- $('#oauth-login').classList.toggle('hidden', Boolean(user));
30
- $('#oauth-consent').classList.toggle('hidden', !user);
31
- $('#status').textContent = user ? `Signed in as ${user.email || 'ReMCP user'}. Review access before continuing.` : '';
32
- });
33
- $('#authorize').onclick = async () => {
34
- if (!currentUser || completing) return;
35
- completing = true;
36
- $('#authorize').disabled = true;
37
- $('#status').textContent = 'Authorizing…';
38
- $('#error').textContent = '';
39
- try {
40
- const body = { id_token: await currentUser.getIdToken() };
41
- for (const key of ['client_id','redirect_uri','response_type','code_challenge','code_challenge_method','scope','state','resource']) {
42
- if (query.has(key)) body[key] = query.get(key);
43
- }
44
- const response = await fetch('/oauth/authorize/complete', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
45
- const data = await response.json();
46
- if (!response.ok) throw new Error(data.error_description || data.error || 'Authorization failed');
47
- location.assign(data.redirect_to);
48
- } catch (error) {
49
- completing = false;
50
- $('#authorize').disabled = false;
51
- $('#status').textContent = '';
52
- showError(error);
53
- }
54
- };