granttap-mcp 0.8.36 → 0.8.37
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/apps/mcp/src/connection-center/widget.html +1 -1
- package/apps/mcp/src/desktop/native-access/README.md +9 -2
- package/apps/mcp/src/desktop/native-access/access.ts +9 -0
- package/apps/mcp/src/desktop/native-access/routes.ts +12 -2
- package/cursor-plugin/README.md +1 -1
- package/cursor-plugin/mcp.json +1 -1
- package/cursor-plugin/stdio-bootstrap.cjs +1 -1
- package/package.json +2 -2
|
@@ -37,7 +37,7 @@ details{border-top:1px solid #8884;padding-top:14px}summary{cursor:pointer;font-
|
|
|
37
37
|
<dt>Last encrypted message from iPhone</dt><dd id="phone">Not observed</dd></dl>
|
|
38
38
|
<h3>Provider readiness</h3><ul id="providers"></ul>
|
|
39
39
|
<p>QR pairs a phone directly. A passkey links this Mac to your GrantTap account so your signed-in phone can discover its computers. Provider sign-in is separate.</p>
|
|
40
|
-
<p>Missing hooks or background sync? Run <code>npm install -g granttap-mcp@0.8.
|
|
40
|
+
<p>Missing hooks or background sync? Run <code>npm install -g granttap-mcp@0.8.37</code> and <code>granttap setup</code> locally.
|
|
41
41
|
A saved pairing alone does not prove your phone or the background helper is online.</p>
|
|
42
42
|
<p><a href="https://granttap.com" target="_blank" rel="noopener noreferrer">Website</a> ·
|
|
43
43
|
<a href="https://granttap.com/privacy" target="_blank" rel="noopener noreferrer">Privacy</a> ·
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
`routes.ts` exposes the existing private desktop bridge on loopback for the
|
|
4
4
|
sandboxed Mac App Store client. This is a desktop-only authorization grant,
|
|
5
|
-
separate from provider MCP OAuth.
|
|
6
|
-
one-time code and S256 PKCE
|
|
5
|
+
separate from provider MCP OAuth. Without a passkey account, a local human
|
|
6
|
+
consent page, a matching one-time consent cookie, one-time code and S256 PKCE
|
|
7
|
+
are required. An explicit foreign Origin is rejected; native browser sessions
|
|
8
|
+
may send an opaque Origin. The callback is fixed to
|
|
7
9
|
`granttap://desktop-auth`; there is no user-selected redirect. Pending consent
|
|
8
10
|
and codes expire after five minutes. A private file stores only token hashes,
|
|
9
11
|
up to eight active grants, expiring after 30 days. The app keeps its token in
|
|
@@ -16,6 +18,11 @@ provider broader MCP scope. The installer/runtime remains outside the sandbox.
|
|
|
16
18
|
`/desktop/account/link` uses the same native grant, verifies the app's fresh
|
|
17
19
|
passkey account session with granttap.com, and stores a revocable machine
|
|
18
20
|
credential locally. It returns only the account and machine identifiers.
|
|
21
|
+
`/desktop/account/authorize` exchanges the Mac app's passkey account session
|
|
22
|
+
for the same scoped local grant. The relay verifies the account and existing
|
|
23
|
+
machine identity, or enrolls this Mac if it has not been linked. The account
|
|
24
|
+
session remains in the app Keychain; cross-origin browser requests cannot read
|
|
25
|
+
it or submit JSON to the loopback service.
|
|
19
26
|
Tests exercise real HTTP and Unix sockets, consent forgery rejection, PKCE,
|
|
20
27
|
one-use codes, expiration, restart and authenticated forwarding.
|
|
21
28
|
|
|
@@ -44,6 +44,15 @@ export class DesktopNativeAccess {
|
|
|
44
44
|
this.codes.delete(code);
|
|
45
45
|
if (!row || row.expires <= this.now() || !/^[A-Za-z0-9_-]{43,128}$/.test(verifier)
|
|
46
46
|
|| digest(verifier) !== row.challenge) throw new Error('Invalid native code');
|
|
47
|
+
return this.issueGrant();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Called only after the local route verifies a passkey account with the relay. */
|
|
51
|
+
issueVerifiedAccountGrant(): string {
|
|
52
|
+
return this.issueGrant();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private issueGrant(): string {
|
|
47
56
|
const token = random();
|
|
48
57
|
const grants = this.grants().filter(grant => grant.expires > this.now()).slice(-7);
|
|
49
58
|
grants.push({ digest: digest(token), expires: this.now() + 30 * 24 * 3_600_000 });
|
|
@@ -7,7 +7,7 @@ import { enrollMacAccount } from '../../../../bridge/src/account-recovery/enroll
|
|
|
7
7
|
import { websiteOrigin } from '../../oauth/consent/website-session';
|
|
8
8
|
|
|
9
9
|
export function installDesktopNativeRoutes(app: Express, socketPath: string, origin: string,
|
|
10
|
-
access = new DesktopNativeAccess()): void {
|
|
10
|
+
access = new DesktopNativeAccess(), enroll = enrollMacAccount): void {
|
|
11
11
|
app.use('/desktop', (_req, res, next) => {
|
|
12
12
|
res.set({ 'Cache-Control': 'no-store', 'Referrer-Policy': 'no-referrer',
|
|
13
13
|
'X-Content-Type-Options': 'nosniff', 'Content-Security-Policy': "default-src 'none'; form-action 'self'; frame-ancestors 'none'" });
|
|
@@ -26,7 +26,9 @@ export function installDesktopNativeRoutes(app: Express, socketPath: string, ori
|
|
|
26
26
|
});
|
|
27
27
|
app.post('/desktop/approve', (req, res) => {
|
|
28
28
|
const confirmation = String(req.body?.confirmation ?? '');
|
|
29
|
-
|
|
29
|
+
const requestOrigin = req.headers.origin;
|
|
30
|
+
if ((requestOrigin !== undefined && requestOrigin !== 'null' && requestOrigin !== origin)
|
|
31
|
+
|| !confirmation ||
|
|
30
32
|
!(req.headers.cookie ?? '').split(';').some(cookie => cookie.trim() === `granttap_desktop_consent=${confirmation}`)) {
|
|
31
33
|
res.status(403).json({ error: 'Native approval requires this local consent page' }); return;
|
|
32
34
|
}
|
|
@@ -53,6 +55,14 @@ export function installDesktopNativeRoutes(app: Express, socketPath: string, ori
|
|
|
53
55
|
res.status(403).json({ error: 'Account link could not be verified' });
|
|
54
56
|
}
|
|
55
57
|
});
|
|
58
|
+
app.post('/desktop/account/authorize', express.json({ limit: '2kb' }), async (req, res) => {
|
|
59
|
+
try {
|
|
60
|
+
await enroll(String(req.body?.accountToken ?? ''), websiteOrigin() ?? 'https://granttap.com');
|
|
61
|
+
res.json({ access_token: access.issueVerifiedAccountGrant() });
|
|
62
|
+
} catch {
|
|
63
|
+
res.status(403).json({ error: 'Passkey account could not authorize this Mac' });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
56
66
|
app.post('/desktop/invoke', (req, res, next) => {
|
|
57
67
|
const auth = String(req.headers.authorization ?? '');
|
|
58
68
|
if (!auth.startsWith('Bearer ') || !access.verify(auth.slice(7))) {
|
package/cursor-plugin/README.md
CHANGED
|
@@ -10,7 +10,7 @@ keeps each phone response tied to the exact originating chat and prompt.
|
|
|
10
10
|
Install the supported CLI on this computer, then run:
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
npm install -g granttap-mcp@0.8.
|
|
13
|
+
npm install -g granttap-mcp@0.8.37
|
|
14
14
|
granttap setup
|
|
15
15
|
granttap status
|
|
16
16
|
```
|
package/cursor-plugin/mcp.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"command": "node",
|
|
5
5
|
"args": [
|
|
6
6
|
"-e",
|
|
7
|
-
"\"use strict\";\n\n// Cursor may start this MCP from any cwd. Execute JavaScript entry points with\n// Node itself: Windows batch shims require shell quoting that differs by host.\nconst { spawn } = require(\"node:child_process\");\nconst { existsSync } = require(\"node:fs\");\nconst { delimiter, dirname, join } = require(\"node:path\");\n\nconst PACKAGE = \"granttap-mcp@0.8.
|
|
7
|
+
"\"use strict\";\n\n// Cursor may start this MCP from any cwd. Execute JavaScript entry points with\n// Node itself: Windows batch shims require shell quoting that differs by host.\nconst { spawn } = require(\"node:child_process\");\nconst { existsSync } = require(\"node:fs\");\nconst { delimiter, dirname, join } = require(\"node:path\");\n\nconst PACKAGE = \"granttap-mcp@0.8.37\";\nconst dryRun = process.env.GRANTTAP_BOOTSTRAP_DRY_RUN === \"1\";\nconst platform = dryRun && process.env.GRANTTAP_BOOTSTRAP_PLATFORM\n ? process.env.GRANTTAP_BOOTSTRAP_PLATFORM : process.platform;\nconst nodeRoot = dryRun && process.env.GRANTTAP_BOOTSTRAP_NODE_ROOT\n ? process.env.GRANTTAP_BOOTSTRAP_NODE_ROOT : dirname(process.execPath);\nconst pathRoots = (process.env.PATH || \"\").split(delimiter).filter(Boolean);\nconst appData = process.env.APPDATA;\nconst roots = [nodeRoot, ...pathRoots, ...(appData ? [join(appData, \"npm\")] : [])];\nconst first = (paths) => paths.find((path) => existsSync(path));\n\nfunction launch() {\n if (platform !== \"win32\") {\n const installed = first(roots.map((root) => join(root, \"granttap-mcp\")));\n if (installed) return { command: installed, args: [] };\n const npx = first(roots.map((root) => join(root, \"npx\"))) || \"npx\";\n return { command: npx, args: [\"-y\", PACKAGE] };\n }\n const installed = first(roots.map((root) =>\n join(root, \"node_modules\", \"granttap-mcp\", \"bin\", \"granttap-mcp.mjs\")));\n if (installed) return { command: process.execPath, args: [installed] };\n const npx = first(roots.map((root) =>\n join(root, \"node_modules\", \"npm\", \"bin\", \"npx-cli.js\")));\n if (npx) return { command: process.execPath, args: [npx, \"-y\", PACKAGE] };\n throw new Error(\"Node.js/npm 20+ is required on this Windows device. Install npm and retry GrantTap.\");\n}\n\nlet target;\ntry { target = launch(); }\ncatch (error) {\n process.stderr.write(`[granttap] ${error.message}\\n`);\n process.exit(1);\n}\n\nif (dryRun) {\n process.stdout.write(JSON.stringify(target));\n process.exit(0);\n}\n\nconst child = spawn(target.command, target.args, { stdio: \"inherit\", windowsHide: true });\nchild.on(\"error\", (error) => {\n process.stderr.write(`[granttap] ${error.message}\\n`);\n process.exit(1);\n});\nchild.on(\"exit\", (code, signal) => {\n if (signal && platform !== \"win32\") {\n try { process.kill(process.pid, signal); } catch { process.exit(code ?? 1); }\n return;\n }\n process.exit(code ?? 1);\n});\n"
|
|
8
8
|
]
|
|
9
9
|
}
|
|
10
10
|
}
|
|
@@ -7,7 +7,7 @@ const { spawn } = require("node:child_process");
|
|
|
7
7
|
const { existsSync } = require("node:fs");
|
|
8
8
|
const { delimiter, dirname, join } = require("node:path");
|
|
9
9
|
|
|
10
|
-
const PACKAGE = "granttap-mcp@0.8.
|
|
10
|
+
const PACKAGE = "granttap-mcp@0.8.37";
|
|
11
11
|
const dryRun = process.env.GRANTTAP_BOOTSTRAP_DRY_RUN === "1";
|
|
12
12
|
const platform = dryRun && process.env.GRANTTAP_BOOTSTRAP_PLATFORM
|
|
13
13
|
? process.env.GRANTTAP_BOOTSTRAP_PLATFORM : process.platform;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "granttap-mcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.37",
|
|
4
4
|
"description": "Personal live control center runtime for local coding agents on iPhone and Apple Watch.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"prepublishOnly": "npm run inventory && npm run structure -- --strict && npm run package:allowlist && npm run test:coverage && npm run typecheck"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
37
37
|
"express": "^5.2.1",
|
|
38
38
|
"qrcode": "^1.5.4",
|
|
39
39
|
"tsx": "^4.23.4",
|