@remcp/remcp 0.1.3 → 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 -105
- 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 -96
- 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 -59
- package/public/copy.js +0 -40
- package/public/docs.html +0 -93
- 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 -134
- package/public/oauth-app.js +0 -63
- package/public/privacy.html +0 -80
- 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 -70
- package/public/site.webmanifest +0 -23
- package/public/style.css +0 -261
- package/public/support.html +0 -75
- package/public/terms.html +0 -77
- 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/public/app.js
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.14.1/firebase-app.js';
|
|
2
|
-
import { getAuth, GoogleAuthProvider, GithubAuthProvider, onAuthStateChanged, signInWithPopup, 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 googleProvider = new GoogleAuthProvider();
|
|
8
|
-
const githubProvider = new GithubAuthProvider();
|
|
9
|
-
googleProvider.setCustomParameters({ prompt: 'select_account' });
|
|
10
|
-
|
|
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) {
|
|
21
|
-
const label = button.querySelector('span');
|
|
22
|
-
button.disabled = true;
|
|
23
|
-
$('#auth-error').textContent = '';
|
|
24
|
-
label.textContent = 'Opening…';
|
|
25
|
-
try { await signInWithPopup(auth, provider); }
|
|
26
|
-
catch (error) { showError(error); }
|
|
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');
|
|
33
|
-
$('#logout').onclick = () => signOut(auth);
|
|
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 paired devices.'; return; }
|
|
74
|
-
for (const device of data.devices) root.append(deviceRow(device));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
$('#pair').onclick = async () => {
|
|
78
|
-
try {
|
|
79
|
-
const pairing = await api('/api/pair/create', { method: 'POST', body: '{}' });
|
|
80
|
-
$('#pairing').classList.remove('hidden');
|
|
81
|
-
$('#pair-command').textContent = pairing.command;
|
|
82
|
-
const expiry = new Intl.DateTimeFormat(undefined, { hour: 'numeric', minute: '2-digit' }).format(new Date(pairing.expiresAt));
|
|
83
|
-
$('#pair-expiry').textContent = `One-time command · expires ${expiry}`;
|
|
84
|
-
} catch (error) { showError(error); }
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
onAuthStateChanged(auth, async user => {
|
|
88
|
-
const signedIn = Boolean(user);
|
|
89
|
-
$('#signed-out').classList.toggle('hidden', signedIn);
|
|
90
|
-
$('#signed-in').classList.toggle('hidden', !signedIn);
|
|
91
|
-
$('#dashboard').classList.toggle('hidden', !signedIn);
|
|
92
|
-
$('#auth-error').textContent = '';
|
|
93
|
-
if (!signedIn) return;
|
|
94
|
-
$('#user-label').textContent = user.email || user.displayName || user.uid;
|
|
95
|
-
try { await refreshDevices(); } catch (error) { showError(error); }
|
|
96
|
-
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/public/authorize.html
DELETED
|
@@ -1,59 +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-013">
|
|
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="/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">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>
|
|
43
|
-
</div>
|
|
44
|
-
<div id="oauth-consent" class="hidden">
|
|
45
|
-
<p class="eyebrow">Access request</p>
|
|
46
|
-
<h2>Authorize ReMCP Access</h2>
|
|
47
|
-
<p class="muted">This MCP client is requesting access to operate computers paired to this ReMCP account.</p>
|
|
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>
|
|
49
|
-
<p class="oauth-meta">Client: <span id="client-id"></span></p>
|
|
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>
|
|
51
|
-
</div>
|
|
52
|
-
<p id="status" class="muted" aria-live="polite"></p>
|
|
53
|
-
<p id="error" class="error" role="alert" aria-live="assertive"></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>
|
|
55
|
-
</section>
|
|
56
|
-
</main>
|
|
57
|
-
<script type="module" src="/oauth-app.js?v=remcp-013"></script>
|
|
58
|
-
</body>
|
|
59
|
-
</html>
|
package/public/copy.js
DELETED
|
@@ -1,40 +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 { await navigator.clipboard.writeText(text); return; } catch {}
|
|
18
|
-
}
|
|
19
|
-
legacyCopy(text);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
document.addEventListener('click', async event => {
|
|
23
|
-
const button = event.target.closest('[data-copy]');
|
|
24
|
-
if (!button) return;
|
|
25
|
-
const target = document.querySelector(button.dataset.copy);
|
|
26
|
-
if (!target) return;
|
|
27
|
-
const text = ('value' in target ? target.value : target.textContent).trim();
|
|
28
|
-
const label = button.getAttribute('aria-label') || 'Copy';
|
|
29
|
-
try {
|
|
30
|
-
await copyText(text);
|
|
31
|
-
button.dataset.copied = 'true';
|
|
32
|
-
button.setAttribute('aria-label', 'Copied');
|
|
33
|
-
} catch {
|
|
34
|
-
button.setAttribute('aria-label', 'Copy failed');
|
|
35
|
-
}
|
|
36
|
-
window.setTimeout(() => {
|
|
37
|
-
delete button.dataset.copied;
|
|
38
|
-
button.setAttribute('aria-label', label);
|
|
39
|
-
}, 1400);
|
|
40
|
-
});
|
package/public/docs.html
DELETED
|
@@ -1,93 +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-013">
|
|
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="/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 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
|
-
|
|
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
|
-
|
|
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>
|
|
46
|
-
|
|
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>
|
|
50
|
-
|
|
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>
|
|
55
|
-
|
|
56
|
-
<h2 id="mcp">4. Add the MCP endpoint</h2>
|
|
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>
|
|
59
|
-
|
|
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>
|
|
63
|
-
|
|
64
|
-
<h2 id="service">Service commands</h2>
|
|
65
|
-
<div class="copy-field multiline"><pre><code id="docs-service">remcp status
|
|
66
|
-
remcp doctor
|
|
67
|
-
remcp install
|
|
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>
|
|
70
|
-
|
|
71
|
-
<h2 id="uninstall">Uninstall</h2>
|
|
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
|
-
|
|
76
|
-
<h2 id="connection-model">Connection model</h2>
|
|
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>
|
|
78
|
-
|
|
79
|
-
<h2 id="self-hosting">Self-hosting</h2>
|
|
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>
|
|
81
|
-
</article>
|
|
82
|
-
</main>
|
|
83
|
-
<footer class="site-footer">
|
|
84
|
-
<div class="wordmark">
|
|
85
|
-
<img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
|
|
86
|
-
<span translate="no">ReMCP</span>
|
|
87
|
-
</div>
|
|
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>
|
|
89
|
-
<p>Open-source remote MCP infrastructure.</p>
|
|
90
|
-
</footer>
|
|
91
|
-
<script type="module" src="/copy.js?v=remcp-013"></script>
|
|
92
|
-
</body>
|
|
93
|
-
</html>
|
package/public/favicon-16x16.png
DELETED
|
Binary file
|
package/public/favicon-32x32.png
DELETED
|
Binary file
|
package/public/favicon-48x48.png
DELETED
|
Binary file
|
package/public/favicon.ico
DELETED
|
Binary file
|
package/public/index.html
DELETED
|
@@ -1,134 +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-013">
|
|
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="/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 for your computers</p>
|
|
37
|
-
<h1>Use your computers from any MCP client.</h1>
|
|
38
|
-
<p class="lede">ReMCP pairs a computer to one authenticated MCP endpoint. The local agent connects outbound, so the computer does not need a public port.</p>
|
|
39
|
-
<div class="hero-actions">
|
|
40
|
-
<a class="button" href="#connect">Open workspace</a>
|
|
41
|
-
<a class="text-link" href="/docs">Read docs <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="Connection facts">
|
|
54
|
-
<div><span>Connection</span><h2>Outbound only</h2><p>The device agent opens the connection to ReMCP. No inbound device port is required.</p></div>
|
|
55
|
-
<div><span>Authentication</span><h2>OAuth + PKCE</h2><p>MCP clients authorize against your ReMCP account. Device credentials are independent and revocable.</p></div>
|
|
56
|
-
<div><span>Control</span><h2>Your paired devices</h2><p>Tool calls are routed only to computers paired with the signed-in account.</p></div>
|
|
57
|
-
</section>
|
|
58
|
-
|
|
59
|
-
<section id="install" class="quickstart">
|
|
60
|
-
<div class="section-heading">
|
|
61
|
-
<p class="eyebrow">Setup</p>
|
|
62
|
-
<h2>Install first. Pair from the workspace.</h2>
|
|
63
|
-
<p>The public install command contains no pairing code. Sign in below to generate the exact one-time command for your computer.</p>
|
|
64
|
-
</div>
|
|
65
|
-
<div class="quickstart-grid">
|
|
66
|
-
<article class="command-card">
|
|
67
|
-
<span>01</span><h3>Install the CLI</h3>
|
|
68
|
-
<div class="copy-field"><code id="install-command">npm install --global @remcp/remcp@latest</code><button class="copy-icon-button" type="button" data-copy="#install-command" 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>
|
|
69
|
-
<p>Installs the current <code>remcp</code> command globally.</p>
|
|
70
|
-
</article>
|
|
71
|
-
<article class="command-card">
|
|
72
|
-
<span>02</span><h3>Pair a computer</h3>
|
|
73
|
-
<p>Sign in to the workspace, choose <strong>Generate pairing command</strong>, then run the generated command on that computer.</p>
|
|
74
|
-
</article>
|
|
75
|
-
<article class="command-card">
|
|
76
|
-
<span>03</span><h3>Verify</h3>
|
|
77
|
-
<div class="copy-field"><code id="status-command">remcp status</code><button class="copy-icon-button" type="button" data-copy="#status-command" aria-label="Copy status 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>
|
|
78
|
-
<p>Checks the installed version, pairing state and server health.</p>
|
|
79
|
-
</article>
|
|
80
|
-
</div>
|
|
81
|
-
<div class="update-line"><span>Already installed?</span><div class="copy-field compact"><code id="update-command">remcp update</code><button class="copy-icon-button" type="button" data-copy="#update-command" 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></div>
|
|
82
|
-
<a class="text-link" href="/docs">Installation and CLI reference <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>Workspace</h2>
|
|
89
|
-
<p>Sign in with Google or GitHub to generate pairing commands and manage your devices.</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 manage your paired computers.</p>
|
|
96
|
-
<div class="provider-stack">
|
|
97
|
-
<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>
|
|
98
|
-
<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>
|
|
99
|
-
</div>
|
|
100
|
-
<p id="auth-error" class="error" role="alert" aria-live="polite"></p>
|
|
101
|
-
</div>
|
|
102
|
-
<div id="signed-in" class="hidden stack">
|
|
103
|
-
<div class="identity"><span class="status-dot" aria-hidden="true"></span><div><strong id="user-label"></strong><small>Authenticated</small></div></div>
|
|
104
|
-
<button id="logout" class="button ghost" type="button">Sign Out</button>
|
|
105
|
-
</div>
|
|
106
|
-
</section>
|
|
107
|
-
<section class="endpoint-pane" aria-labelledby="endpoint-title">
|
|
108
|
-
<div><h3 id="endpoint-title">MCP endpoint</h3><p class="muted">Use this HTTPS endpoint when adding ReMCP to an MCP client.</p></div>
|
|
109
|
-
<div class="copy-field"><code id="mcp-url">https://remcp.delio24.com/mcp</code><button class="copy-icon-button" type="button" data-copy="#mcp-url" 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>
|
|
110
|
-
<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>
|
|
111
|
-
</section>
|
|
112
|
-
</div>
|
|
113
|
-
<section id="dashboard" class="hidden device-workspace" aria-label="Device management">
|
|
114
|
-
<div class="pair-panel">
|
|
115
|
-
<div><p class="eyebrow">Add a computer</p><h3>Generate pairing command</h3><p class="muted">The generated command contains a short-lived one-time code. Run it only on the computer you want to pair.</p></div>
|
|
116
|
-
<button id="pair" class="button" type="button">Generate pairing command</button>
|
|
117
|
-
<div id="pairing" class="hidden pairing-output"><div class="copy-field"><code id="pair-command"></code><button class="copy-icon-button" type="button" data-copy="#pair-command" aria-label="Copy pairing 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><p id="pair-expiry" class="muted"></p></div>
|
|
118
|
-
</div>
|
|
119
|
-
<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>
|
|
120
|
-
</section>
|
|
121
|
-
</section>
|
|
122
|
-
</main>
|
|
123
|
-
<footer class="site-footer">
|
|
124
|
-
<div class="wordmark">
|
|
125
|
-
<img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
|
|
126
|
-
<span translate="no">ReMCP</span>
|
|
127
|
-
</div>
|
|
128
|
-
<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>
|
|
129
|
-
<p>Open-source remote MCP infrastructure.</p>
|
|
130
|
-
</footer>
|
|
131
|
-
<script type="module" src="/copy.js?v=remcp-013"></script>
|
|
132
|
-
<script type="module" src="/app.js?v=remcp-013"></script>
|
|
133
|
-
</body>
|
|
134
|
-
</html>
|
package/public/oauth-app.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.14.1/firebase-app.js';
|
|
2
|
-
import { getAuth, GoogleAuthProvider, GithubAuthProvider, onAuthStateChanged, signInWithPopup } 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 googleProvider = new GoogleAuthProvider();
|
|
9
|
-
const githubProvider = new GithubAuthProvider();
|
|
10
|
-
googleProvider.setCustomParameters({ prompt: 'select_account' });
|
|
11
|
-
let currentUser = null;
|
|
12
|
-
let completing = false;
|
|
13
|
-
|
|
14
|
-
const showError = error => {
|
|
15
|
-
const code = error?.code || '';
|
|
16
|
-
if (code === 'auth/popup-closed-by-user' || code === 'auth/cancelled-popup-request') return;
|
|
17
|
-
$('#error').textContent = code === 'auth/account-exists-with-different-credential'
|
|
18
|
-
? 'This email is already linked to another sign-in provider.'
|
|
19
|
-
: error?.message || String(error);
|
|
20
|
-
};
|
|
21
|
-
async function signIn(button, provider, idleLabel) {
|
|
22
|
-
const label = button.querySelector('span');
|
|
23
|
-
button.disabled = true;
|
|
24
|
-
$('#error').textContent = '';
|
|
25
|
-
label.textContent = 'Opening…';
|
|
26
|
-
try { await signInWithPopup(auth, provider); }
|
|
27
|
-
catch (error) { showError(error); }
|
|
28
|
-
finally { button.disabled = false; label.textContent = idleLabel; }
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
$('#client-id').textContent = query.get('client_id') || 'MCP client';
|
|
32
|
-
$('#google').onclick = () => signIn($('#google'), googleProvider, 'Continue with Google');
|
|
33
|
-
$('#github').onclick = () => signIn($('#github'), githubProvider, 'Continue with GitHub');
|
|
34
|
-
|
|
35
|
-
onAuthStateChanged(auth, user => {
|
|
36
|
-
currentUser = user;
|
|
37
|
-
$('#oauth-login').classList.toggle('hidden', Boolean(user));
|
|
38
|
-
$('#oauth-consent').classList.toggle('hidden', !user);
|
|
39
|
-
$('#status').textContent = user ? `Signed in as ${user.email || user.displayName || 'ReMCP user'}.` : '';
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
$('#authorize').onclick = async () => {
|
|
43
|
-
if (!currentUser || completing) return;
|
|
44
|
-
completing = true;
|
|
45
|
-
$('#authorize').disabled = true;
|
|
46
|
-
$('#status').textContent = 'Authorizing…';
|
|
47
|
-
$('#error').textContent = '';
|
|
48
|
-
try {
|
|
49
|
-
const body = { id_token: await currentUser.getIdToken() };
|
|
50
|
-
for (const key of ['client_id','redirect_uri','response_type','code_challenge','code_challenge_method','scope','state','resource']) {
|
|
51
|
-
if (query.has(key)) body[key] = query.get(key);
|
|
52
|
-
}
|
|
53
|
-
const response = await fetch('/oauth/authorize/complete', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
|
|
54
|
-
const data = await response.json();
|
|
55
|
-
if (!response.ok) throw new Error(data.error_description || data.error || 'Authorization failed');
|
|
56
|
-
location.assign(data.redirect_to);
|
|
57
|
-
} catch (error) {
|
|
58
|
-
completing = false;
|
|
59
|
-
$('#authorize').disabled = false;
|
|
60
|
-
$('#status').textContent = '';
|
|
61
|
-
showError(error);
|
|
62
|
-
}
|
|
63
|
-
};
|