@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 +85 -42
- package/assets/remcp-icon.png +0 -0
- package/mcp.json +9 -0
- package/package.json +4 -2
- package/plugin.json +37 -0
- package/public/app.js +25 -24
- package/public/assets/art/hero-relay.webp +0 -0
- package/public/assets/art/relay-detail.webp +0 -0
- package/public/authorize.html +10 -21
- package/public/copy.js +9 -18
- package/public/docs.html +31 -32
- package/public/index.html +38 -58
- package/public/oauth-app.js +22 -13
- package/public/privacy.html +34 -13
- package/public/security.html +25 -12
- package/public/style.css +34 -32
- package/public/support.html +30 -14
- package/public/terms.html +31 -14
- package/skills/remcp-operator/SKILL.md +1 -1
- package/src/auth.mjs +1 -0
- package/src/config.mjs +1 -1
- package/src/db.mjs +8 -5
- package/src/mcp.mjs +1 -1
- package/src/oauth.mjs +24 -5
- package/src/util.mjs +1 -1
- package/chatgpt-app-submission.json +0 -356
package/public/support.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<title>Support · ReMCP</title>
|
|
9
9
|
<meta name="description" content="Troubleshoot ReMCP sign-in, device pairing, agent connectivity and MCP authorization.">
|
|
10
10
|
<link rel="preconnect" href="https://www.gstatic.com" crossorigin>
|
|
11
|
-
<link rel="stylesheet" href="/style.css?v=remcp-
|
|
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,22 +28,38 @@
|
|
|
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="/
|
|
31
|
+
<nav aria-label="Primary"><a href="/docs">Docs</a><a href="/security">Security</a><a href="/support" aria-current="page">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">Setup</a><a href="/docs#install">Install</a><a href="/docs#update">Update</a><a href="/security">Security</a><a href="/support" aria-current="page">Support</a><a href="/privacy">Privacy</a><a href="/terms">Terms</a></aside>
|
|
35
35
|
<article class="prose">
|
|
36
36
|
<p class="eyebrow">Support</p>
|
|
37
|
-
<h1>
|
|
38
|
-
<p>
|
|
39
|
-
|
|
40
|
-
<h2>
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
<h2>
|
|
44
|
-
remcp
|
|
45
|
-
<
|
|
46
|
-
|
|
37
|
+
<h1>Troubleshooting.</h1>
|
|
38
|
+
<p>Check the layer that is failing: sign-in, pairing, the local agent, or MCP authorization.</p>
|
|
39
|
+
|
|
40
|
+
<h2>Sign-in does not open</h2>
|
|
41
|
+
<p>Use <strong>Continue with Google</strong> or <strong>Continue with GitHub</strong>. ReMCP does not use email/password sign-in on the hosted site. If a popup is blocked, allow popups for <code>remcp.delio24.com</code> and try again.</p>
|
|
42
|
+
|
|
43
|
+
<h2>The <code>remcp</code> command is missing</h2>
|
|
44
|
+
<div class="copy-field"><code id="support-install">npm install --global @remcp/remcp@latest</code><button class="copy-icon-button" type="button" data-copy="#support-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>After installation, pair the computer from the signed-in workspace. The public install command alone does not pair a device.</p>
|
|
46
|
+
|
|
47
|
+
<h2>Pairing command expired</h2>
|
|
48
|
+
<p>Open the workspace and generate a new command. Each pairing command contains a one-time code and expires automatically.</p>
|
|
49
|
+
|
|
50
|
+
<h2>Device shows offline</h2>
|
|
51
|
+
<div class="copy-field"><code id="support-status">remcp status</code><button class="copy-icon-button" type="button" data-copy="#support-status" 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>
|
|
52
|
+
<p>Confirm the user service is running and outbound HTTPS/WSS access to <code>remcp.delio24.com</code> is allowed. Re-pair only if the device credential was revoked or the local pairing state was removed.</p>
|
|
53
|
+
|
|
54
|
+
<h2>OAuth does not complete</h2>
|
|
55
|
+
<p>Return to the authorization page, sign in with the intended ReMCP account, review the requested scopes, and choose <strong>Authorize ReMCP Access</strong>.</p>
|
|
56
|
+
|
|
57
|
+
<h2>MCP tool cannot run</h2>
|
|
58
|
+
<p>Confirm the target device is online. A device tool needs the current ReMCP device id; clients can obtain it with <code>list_devices</code>.</p>
|
|
59
|
+
|
|
60
|
+
<h2>Update or repair</h2>
|
|
61
|
+
<div class="copy-field"><code id="support-update">remcp update</code><button class="copy-icon-button" type="button" data-copy="#support-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>If the CLI is paired but its background service is missing, run <code>remcp install</code>.</p>
|
|
47
63
|
</article>
|
|
48
64
|
</main>
|
|
49
65
|
<footer class="site-footer">
|
|
@@ -51,9 +67,9 @@ remcp status</code></pre><button class="copy-button code-copy" type="button" dat
|
|
|
51
67
|
<img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
|
|
52
68
|
<span translate="no">ReMCP</span>
|
|
53
69
|
</div>
|
|
54
|
-
<nav aria-label="Footer"><a href="/docs">Docs</a><a href="/
|
|
70
|
+
<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>
|
|
55
71
|
<p>Open-source remote MCP infrastructure.</p>
|
|
56
72
|
</footer>
|
|
57
|
-
<script type="module" src="/copy.js?v=remcp-
|
|
73
|
+
<script type="module" src="/copy.js?v=remcp-013"></script>
|
|
58
74
|
</body>
|
|
59
75
|
</html>
|
package/public/terms.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<title>Terms of Service · ReMCP</title>
|
|
9
9
|
<meta name="description" content="Terms for using the hosted ReMCP service and its remote-computer capabilities.">
|
|
10
10
|
<link rel="preconnect" href="https://www.gstatic.com" crossorigin>
|
|
11
|
-
<link rel="stylesheet" href="/style.css?v=remcp-
|
|
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,24 +28,41 @@
|
|
|
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="/
|
|
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="main" class="doc-shell">
|
|
34
34
|
<aside class="doc-nav" aria-label="Documentation"><a href="/docs">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" aria-current="page">Terms</a></aside>
|
|
35
35
|
<article class="prose">
|
|
36
36
|
<p class="eyebrow">Legal</p>
|
|
37
37
|
<h1>Terms of Service</h1>
|
|
38
|
-
<p><strong>Effective September 16, 2026.</strong> These Terms apply to the hosted ReMCP service at remcp.delio24.com
|
|
39
|
-
|
|
40
|
-
<h2>
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
<h2>
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
<h2>
|
|
47
|
-
<
|
|
48
|
-
|
|
38
|
+
<p><strong>Effective September 16, 2026.</strong> These Terms apply to the hosted ReMCP service at <code>remcp.delio24.com</code>. The open-source software can also be self-hosted under its software licenses.</p>
|
|
39
|
+
|
|
40
|
+
<h2>Authorized systems only</h2>
|
|
41
|
+
<p>You may pair and control only computers and accounts that you own or are authorized to administer. You are responsible for protecting your sign-in account and revoking devices or OAuth grants you no longer trust.</p>
|
|
42
|
+
|
|
43
|
+
<h2>Remote actions</h2>
|
|
44
|
+
<p>ReMCP can read and modify files and can start, interact with or terminate processes. Some commands can affect external systems. Review consequential actions and maintain appropriate backups and operating-system permissions.</p>
|
|
45
|
+
|
|
46
|
+
<h2>Acceptable use</h2>
|
|
47
|
+
<p>Do not use ReMCP to access systems without authorization, steal credentials, distribute malware, evade security controls, interfere with other users, or violate applicable law or third-party rights.</p>
|
|
48
|
+
|
|
49
|
+
<h2>Sign-in providers</h2>
|
|
50
|
+
<p>The hosted service uses Firebase Authentication with Google and GitHub sign-in. Those providers and other infrastructure dependencies are governed by their own terms. ReMCP does not grant rights to third-party systems accessible from a paired computer.</p>
|
|
51
|
+
|
|
52
|
+
<h2>Service operation</h2>
|
|
53
|
+
<p>The hosted service may be updated, rate-limited, suspended or discontinued for security, abuse prevention, maintenance or legal reasons. Uninterrupted availability is not guaranteed. Self-hosted deployments are operated and secured by their administrators.</p>
|
|
54
|
+
|
|
55
|
+
<h2>Open-source software</h2>
|
|
56
|
+
<p>ReMCP and its dependencies include open-source software. Their license notices govern those software components independently of these hosted-service Terms.</p>
|
|
57
|
+
|
|
58
|
+
<h2>No warranty</h2>
|
|
59
|
+
<p>To the maximum extent permitted by applicable law, the service and software are provided “as is” and “as available,” without warranties of uninterrupted operation or fitness for a particular purpose. Rights that cannot legally be excluded remain unaffected.</p>
|
|
60
|
+
|
|
61
|
+
<h2>Responsibility for actions</h2>
|
|
62
|
+
<p>You are responsible for commands and changes made through devices you authorize. To the maximum extent permitted by law, ReMCP is not responsible for indirect or consequential loss arising from device changes, lost data, third-party systems or service interruption.</p>
|
|
63
|
+
|
|
64
|
+
<h2>Termination and changes</h2>
|
|
65
|
+
<p>You may stop using the service and revoke devices at any time. Access may be suspended for security, abuse or legal reasons. Material changes to these Terms will be published at this URL with a new effective date.</p>
|
|
49
66
|
</article>
|
|
50
67
|
</main>
|
|
51
68
|
<footer class="site-footer">
|
|
@@ -53,7 +70,7 @@
|
|
|
53
70
|
<img class="brand-mark" src="/android-chrome-192x192.png" width="30" height="30" alt="" aria-hidden="true">
|
|
54
71
|
<span translate="no">ReMCP</span>
|
|
55
72
|
</div>
|
|
56
|
-
<nav aria-label="Footer"><a href="/docs">Docs</a><a href="/
|
|
73
|
+
<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>
|
|
57
74
|
<p>Open-source remote MCP infrastructure.</p>
|
|
58
75
|
</footer>
|
|
59
76
|
</body>
|
|
@@ -33,4 +33,4 @@ Use `start_process` once and then `read_process_output` or `interact_with_proces
|
|
|
33
33
|
|
|
34
34
|
## Out-of-scope requests
|
|
35
35
|
|
|
36
|
-
Do not use ReMCP to access a computer the user has not paired or is not authorized to control. Do not ask for passwords, MFA codes, private keys, or other credentials through ReMCP
|
|
36
|
+
Do not use ReMCP to access a computer the user has not paired or is not authorized to control. Do not ask for or process passwords, MFA codes, private keys, API keys, payment-card data, protected health information, government identifiers, or other restricted credentials/data through ReMCP tools. If a requested file or command would expose those categories, ask the user to use a safer local workflow instead.
|
package/src/auth.mjs
CHANGED
package/src/config.mjs
CHANGED
|
@@ -38,7 +38,7 @@ export const config = Object.freeze({
|
|
|
38
38
|
refreshTtlSeconds: Number(process.env.REFRESH_TTL_SECONDS || 2592000),
|
|
39
39
|
rpcTimeoutMs: Number(process.env.RPC_TIMEOUT_MS || 120000),
|
|
40
40
|
domainVerificationToken: (process.env.OPENAI_DOMAIN_VERIFICATION_TOKEN || '').trim(),
|
|
41
|
-
domainVerificationPath: (process.env.OPENAI_DOMAIN_VERIFICATION_PATH || '/.well-known/openai-
|
|
41
|
+
domainVerificationPath: (process.env.OPENAI_DOMAIN_VERIFICATION_PATH || '/.well-known/openai-apps-challenge').trim(),
|
|
42
42
|
firebase: {
|
|
43
43
|
apiKey: required('FIREBASE_API_KEY'),
|
|
44
44
|
authDomain: required('FIREBASE_AUTH_DOMAIN'),
|
package/src/db.mjs
CHANGED
|
@@ -9,7 +9,7 @@ export const db = new DatabaseSync(join(config.dataDir, 'remcp.sqlite'));
|
|
|
9
9
|
db.exec(`PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON; PRAGMA busy_timeout=5000;`);
|
|
10
10
|
db.exec(`
|
|
11
11
|
CREATE TABLE IF NOT EXISTS users (
|
|
12
|
-
uid TEXT PRIMARY KEY, email TEXT, name TEXT, created_at INTEGER NOT NULL, last_login_at INTEGER NOT NULL
|
|
12
|
+
uid TEXT PRIMARY KEY, email TEXT, name TEXT, email_verified INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL, last_login_at INTEGER NOT NULL
|
|
13
13
|
);
|
|
14
14
|
CREATE TABLE IF NOT EXISTS pair_codes (
|
|
15
15
|
code_hash TEXT PRIMARY KEY, uid TEXT NOT NULL REFERENCES users(uid) ON DELETE CASCADE,
|
|
@@ -34,12 +34,15 @@ CREATE TABLE IF NOT EXISTS oauth_refresh_tokens (
|
|
|
34
34
|
scope TEXT NOT NULL, expires_at INTEGER NOT NULL, revoked_at INTEGER
|
|
35
35
|
);
|
|
36
36
|
`);
|
|
37
|
+
try { db.exec('ALTER TABLE users ADD COLUMN email_verified INTEGER NOT NULL DEFAULT 0'); } catch {}
|
|
37
38
|
|
|
38
|
-
export
|
|
39
|
+
export const getUser = uid => db.prepare('SELECT * FROM users WHERE uid=?').get(uid);
|
|
40
|
+
|
|
41
|
+
export function upsertUser({ uid, email = '', name = '', emailVerified = false }) {
|
|
39
42
|
const t = now();
|
|
40
|
-
db.prepare(`INSERT INTO users(uid,email,name,created_at,last_login_at) VALUES(
|
|
41
|
-
ON CONFLICT(uid) DO UPDATE SET email=excluded.email,name=excluded.name,last_login_at=excluded.last_login_at`)
|
|
42
|
-
.run(uid, email, name, t, t);
|
|
43
|
+
db.prepare(`INSERT INTO users(uid,email,name,email_verified,created_at,last_login_at) VALUES(?,?,?,?,?,?)
|
|
44
|
+
ON CONFLICT(uid) DO UPDATE SET email=excluded.email,name=excluded.name,email_verified=excluded.email_verified,last_login_at=excluded.last_login_at`)
|
|
45
|
+
.run(uid, email, name, emailVerified ? 1 : 0, t, t);
|
|
43
46
|
return db.prepare('SELECT * FROM users WHERE uid=?').get(uid);
|
|
44
47
|
}
|
|
45
48
|
|
package/src/mcp.mjs
CHANGED
|
@@ -57,7 +57,7 @@ function publicTool(tool) {
|
|
|
57
57
|
readOnlyHint: tool.name === 'start_search' ? false : upstream.readOnlyHint === true,
|
|
58
58
|
destructiveHint: destructive.has(tool.name),
|
|
59
59
|
idempotentHint: upstream.idempotentHint === true,
|
|
60
|
-
openWorldHint: tool.name === 'read_file'
|
|
60
|
+
openWorldHint: openWorld.has(tool.name) || tool.name === 'read_file' || upstream.openWorldHint === true,
|
|
61
61
|
},
|
|
62
62
|
};
|
|
63
63
|
}
|
package/src/oauth.mjs
CHANGED
|
@@ -2,9 +2,9 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import express from 'express';
|
|
3
3
|
import { rateLimit } from 'express-rate-limit';
|
|
4
4
|
import { config } from './config.mjs';
|
|
5
|
-
import { verifyFirebaseToken, signMcpAccessToken } from './auth.mjs';
|
|
6
|
-
import { createOauthCode, consumeOauthCode, getOauthClient, getRefreshToken, revokeRefreshToken, saveOauthClient, saveRefreshToken, upsertUser } from './db.mjs';
|
|
7
|
-
import { normalizeScope, now, randomId, randomToken, sha256, validRedirectUri } from './util.mjs';
|
|
5
|
+
import { verifyFirebaseToken, signMcpAccessToken, verifyMcpAccessToken } from './auth.mjs';
|
|
6
|
+
import { createOauthCode, consumeOauthCode, getOauthClient, getRefreshToken, getUser, revokeRefreshToken, saveOauthClient, saveRefreshToken, upsertUser } from './db.mjs';
|
|
7
|
+
import { bearer, normalizeScope, now, randomId, randomToken, sha256, validRedirectUri } from './util.mjs';
|
|
8
8
|
|
|
9
9
|
const CODE_TTL_MS = 5 * 60 * 1000;
|
|
10
10
|
const router = express.Router();
|
|
@@ -18,12 +18,13 @@ function metadata() {
|
|
|
18
18
|
authorization_endpoint: endpoint('/oauth/authorize'),
|
|
19
19
|
token_endpoint: endpoint('/oauth/token'),
|
|
20
20
|
registration_endpoint: endpoint('/oauth/register'),
|
|
21
|
+
userinfo_endpoint: endpoint('/oauth/userinfo'),
|
|
21
22
|
revocation_endpoint: endpoint('/oauth/revoke'),
|
|
22
23
|
response_types_supported: ['code'],
|
|
23
24
|
grant_types_supported: ['authorization_code', 'refresh_token'],
|
|
24
25
|
token_endpoint_auth_methods_supported: ['none'],
|
|
25
26
|
code_challenge_methods_supported: ['S256'],
|
|
26
|
-
scopes_supported: ['remcp:control', 'offline_access'],
|
|
27
|
+
scopes_supported: ['openid', 'email', 'remcp:control', 'offline_access'],
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -32,7 +33,7 @@ function protectedResource() {
|
|
|
32
33
|
resource: endpoint('/mcp'),
|
|
33
34
|
authorization_servers: [config.publicUrl],
|
|
34
35
|
bearer_methods_supported: ['header'],
|
|
35
|
-
scopes_supported: ['remcp:control', 'offline_access'],
|
|
36
|
+
scopes_supported: ['openid', 'email', 'remcp:control', 'offline_access'],
|
|
36
37
|
resource_documentation: config.publicUrl,
|
|
37
38
|
resource_policy_uri: endpoint('/privacy'),
|
|
38
39
|
resource_tos_uri: endpoint('/terms'),
|
|
@@ -136,6 +137,24 @@ router.post('/oauth/token', tokenLimiter, async (req, res) => {
|
|
|
136
137
|
}
|
|
137
138
|
});
|
|
138
139
|
|
|
140
|
+
|
|
141
|
+
router.get('/oauth/userinfo', oauthLimiter, async (req, res) => {
|
|
142
|
+
noStore(res);
|
|
143
|
+
try {
|
|
144
|
+
const token = bearer(req);
|
|
145
|
+
if (!token) throw new Error('missing_token');
|
|
146
|
+
const auth = await verifyMcpAccessToken(token);
|
|
147
|
+
const scopes = new Set(auth.scope.split(/\s+/).filter(Boolean));
|
|
148
|
+
if (!scopes.has('openid') || !scopes.has('email')) throw new Error('insufficient_scope');
|
|
149
|
+
const user = getUser(auth.uid);
|
|
150
|
+
if (!user?.email || user.email_verified !== 1) throw new Error('verified_email_required');
|
|
151
|
+
res.json({ sub: auth.uid, email: user.email, email_verified: true, ...(user.name ? { name: user.name } : {}) });
|
|
152
|
+
} catch (error) {
|
|
153
|
+
res.setHeader('WWW-Authenticate', 'Bearer error="invalid_token"');
|
|
154
|
+
res.status(401).json({ error: 'invalid_token', error_description: error.message });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
139
158
|
router.post('/oauth/revoke', oauthLimiter, (req, res) => {
|
|
140
159
|
const token = String(req.body?.token || '');
|
|
141
160
|
if (token) revokeRefreshToken(sha256(token));
|
package/src/util.mjs
CHANGED
|
@@ -9,7 +9,7 @@ export const bearer = req => {
|
|
|
9
9
|
return value.startsWith('Bearer ') ? value.slice(7).trim() : '';
|
|
10
10
|
};
|
|
11
11
|
export function normalizeScope(value = '') {
|
|
12
|
-
const allowed = new Set(['openid', 'offline_access', 'remcp:control']);
|
|
12
|
+
const allowed = new Set(['openid', 'email', 'offline_access', 'remcp:control']);
|
|
13
13
|
const scopes = String(value).split(/\s+/).filter(Boolean).filter(scope => allowed.has(scope));
|
|
14
14
|
if (!scopes.includes('remcp:control')) scopes.push('remcp:control');
|
|
15
15
|
if (!scopes.includes('offline_access')) scopes.push('offline_access');
|