@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.
@@ -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-012">
11
+ <link rel="stylesheet" href="/style.css?v=remcp-013">
12
12
  <meta property="og:site_name" content="ReMCP">
13
13
  <meta property="og:image" content="https://remcp.delio24.com/assets/og-remcp.png">
14
14
  <link rel="icon" href="/favicon.ico" sizes="any">
@@ -28,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="/docs#install">Install</a><a href="/docs#update">Update</a><a href="/security">Security</a><a href="/support" aria-current="page">Support</a></nav>
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>Troubleshoot ReMCP.</h1>
38
- <p>Most connection failures fall into one of three layers: account authentication, device pairing, or the local device agent.</p>
39
- <h2>Device shows offline</h2><p>Verify the ReMCP agent service is running and outbound HTTPS/WSS access to <code>remcp.delio24.com</code> is allowed. Re-pair the device if its credential was revoked.</p>
40
- <h2>OAuth does not complete</h2><p>Return to the authorization screen, sign in to the intended ReMCP account, review the requested access, and choose <strong>Authorize ReMCP Access</strong>.</p>
41
- <h2>MCP tool cannot run</h2><p>Confirm the target computer is online and use <code>list_devices</code> to obtain its current device id. Device tools require that id.</p>
42
- <h2>Pairing code expired</h2><p>Generate a new pairing command from the workspace. Pairing codes are intentionally short-lived and single-use.</p>
43
- <h2>Check the installed agent</h2><div class="code-row"><pre><code id="support-command-1">remcp --version
44
- remcp status</code></pre><button class="copy-button code-copy" type="button" data-copy="#support-command-1" aria-label="Copy command" aria-live="polite">Copy</button></div><p>If <code>remcp</code> is not found, repeat the <a href="/docs#install">install command</a>. The first npx invocation is temporary; <code>--install</code> creates the persistent CLI.</p>
45
- <h2>Update or repair</h2><div class="code-row"><pre><code id="support-command-2">remcp update</code></pre><button class="copy-button code-copy" type="button" data-copy="#support-command-2" aria-label="Copy command" aria-live="polite">Copy</button></div><p>This refreshes the published CLI and restarts the installed user service. For an already paired device whose service is missing, run <code>remcp install</code>.</p>
46
- <h2>Need deployment help?</h2><p>For a self-hosted deployment, contact the operator who provisioned your ReMCP instance and include the affected layer: sign-in, pairing, agent connectivity, or MCP authorization.</p>
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="/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>
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-012"></script>
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-012">
11
+ <link rel="stylesheet" href="/style.css?v=remcp-013">
12
12
  <meta property="og:site_name" content="ReMCP">
13
13
  <meta property="og:image" content="https://remcp.delio24.com/assets/og-remcp.png">
14
14
  <link rel="icon" href="/favicon.ico" sizes="any">
@@ -28,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="/docs#install">Install</a><a href="/docs#update">Update</a><a href="/security">Security</a><a href="/support">Support</a></nav>
31
+ <nav aria-label="Primary"><a href="/docs">Docs</a><a href="/security">Security</a><a href="/support">Support</a></nav>
32
32
  </header>
33
33
  <main id="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. The open-source software may also be self-hosted under its applicable software license.</p>
39
- <h2>Your account and devices</h2><p>You may connect only computers and accounts that you own or are authorized to administer. You are responsible for protecting your sign-in account, reviewing paired devices, and revoking devices or OAuth grants you no longer trust.</p>
40
- <h2>Remote actions</h2><p>ReMCP can expose powerful local operations, including reading and modifying files and starting or terminating processes. Some actions can be destructive or can affect external systems when a command run on your computer reaches them. You are responsible for reviewing and authorizing requested actions and for maintaining appropriate backups and operating-system permissions.</p>
41
- <h2>Acceptable use</h2><p>You may not use ReMCP to access systems without authorization, distribute malware, evade security controls, interfere with other users, conduct credential theft, or violate applicable law or third-party rights. You may not attempt to access devices belonging to another ReMCP account.</p>
42
- <h2>Service operation</h2><p>The hosted service may be updated, rate-limited, suspended or discontinued to protect users, respond to abuse, maintain security or operate the service. ReMCP does not guarantee uninterrupted availability. Self-hosted deployments are operated and secured by their administrators.</p>
43
- <h2>Third-party services and software</h2><p>Authentication may rely on Firebase/Google, and device functionality may rely on open-source dependencies. Those services and packages are subject to their own terms and licenses. ReMCP does not grant rights to third-party systems that you access from a paired device.</p>
44
- <h2>Open-source software</h2><p>The ReMCP source distribution includes software under open-source licenses. License notices in the source distribution govern those components independently of these hosted-service Terms.</p>
45
- <h2>No warranty</h2><p>To the maximum extent permitted by applicable law, the service and software are provided on an “as is” and “as available” basis without warranties of uninterrupted operation, fitness for a particular purpose, or freedom from all defects. Nothing in these Terms limits rights that cannot legally be excluded.</p>
46
- <h2>Responsibility for losses</h2><p>You are responsible for commands and changes made through devices you authorize. To the maximum extent permitted by applicable law, ReMCP is not responsible for indirect, incidental or consequential losses arising from device changes, lost data, third-party systems or service interruptions. Mandatory consumer or statutory rights remain unaffected.</p>
47
- <h2>Termination</h2><p>You may stop using the service and revoke your devices at any time. Access may be suspended for security, abuse or legal reasons. Provisions that by their nature should survive termination, including responsibility for prior actions and license obligations, continue to apply.</p>
48
- <h2>Changes</h2><p>Material changes to these Terms will be published at this URL with a new effective date. Continued use after changes become effective constitutes acceptance where permitted by law.</p>
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="/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>
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 tool inputs.
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
@@ -18,6 +18,7 @@ export async function verifyFirebaseToken(token) {
18
18
  uid,
19
19
  firebaseUid: payload.sub,
20
20
  email,
21
+ emailVerified: payload.email_verified === true,
21
22
  name: typeof payload.name === 'string' ? payload.name : '',
22
23
  };
23
24
  }
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-domain-verification').trim(),
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 function upsertUser({ uid, email = '', name = '' }) {
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' ? false : (openWorld.has(tool.name) || upstream.openWorldHint === true),
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');