granttap-mcp 0.1.0 → 0.2.0

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 CHANGED
@@ -14,6 +14,15 @@ GrantTap MCP complements the agents' native permission hooks:
14
14
  a tool call and waits for Allow or Deny.
15
15
  - Both channels use the same end-to-end encrypted GrantTap pairing.
16
16
 
17
+ | Visible iPhone activity | Approval on Apple Watch |
18
+ | --- | --- |
19
+ | ![GrantTap activity on iPhone](docs/images/phone-activity.png) | ![GrantTap approval on Apple Watch](docs/images/watch-approval.png) |
20
+
21
+ The iPhone and Apple Watch app is preparing for App Store release. Current
22
+ status and target date are published at [granttap.com](https://granttap.com).
23
+ Support, privacy, terms, and license information are linked below and from the
24
+ app's About screen.
25
+
17
26
  ## Install
18
27
 
19
28
  The shortest MCP-only setup is:
@@ -23,18 +32,23 @@ codex mcp add granttap -- npx -y granttap-mcp
23
32
  claude mcp add granttap -- npx -y granttap-mcp
24
33
  ```
25
34
 
26
- For approval hooks, install the command globally so the hook path remains
27
- stable, then register both supported agents:
35
+ Pair this computer with the GrantTap app, then register the approval hooks:
28
36
 
29
37
  ```bash
30
38
  npm install -g granttap-mcp
39
+ granttap-mcp connect
31
40
  granttap-mcp setup
32
41
  ```
33
42
 
34
- The GrantTap app and desktop bridge must already be paired. The pairing lives
35
- locally in `~/.granttap/machine.json`; this package reads it but never uploads
36
- the secret key. Existing beta installations using `~/.nodvox/` are migrated
37
- automatically.
43
+ `connect` prints a QR and an optional one-time eight-character code. It uses
44
+ the production zero-knowledge relay by default; pass your own `wss://` URL as
45
+ the first argument to self-host. The pairing lives locally in
46
+ `~/.granttap/machine.json`; this package never uploads the device secret key.
47
+ Existing beta installations using `~/.nodvox/` are migrated automatically.
48
+
49
+ `connect` also registers both hooks. `setup` is an idempotent standalone
50
+ command for upgrades: it preserves unrelated settings and writes a backup
51
+ before changing a configuration file.
38
52
 
39
53
  ## MCP tools
40
54
 
@@ -45,6 +59,14 @@ automatically.
45
59
  | `notify` | Sends a non-blocking status message |
46
60
  | `setup` | Registers the Claude Code and Codex approval hooks |
47
61
 
62
+ ## CLI commands
63
+
64
+ | Command | Purpose |
65
+ | --- | --- |
66
+ | *(no command)* | Starts the GrantTap MCP stdio server |
67
+ | `connect [relayUrl]` | Creates an E2EE pairing and prints a QR/short code |
68
+ | `setup` | Registers the Claude Code and Codex approval hooks |
69
+
48
70
  The default answer timeout is three minutes. Override it with
49
71
  `GRANTTAP_ASK_TIMEOUT_MS`.
50
72
 
@@ -83,6 +105,10 @@ as `*.bak-granttap`.
83
105
  ## Related
84
106
 
85
107
  - Product: [granttap.com](https://granttap.com)
108
+ - npm: [granttap-mcp](https://www.npmjs.com/package/granttap-mcp)
86
109
  - Relay: [sergii-ziborov/granttap-relay](https://github.com/sergii-ziborov/granttap-relay)
110
+ - Privacy: [granttap.com/privacy](https://granttap.com/privacy)
111
+ - Support: [granttap.com/support](https://granttap.com/support)
112
+ - Security policy: [SECURITY.md](SECURITY.md)
87
113
 
88
114
  GrantTap is not affiliated with Anthropic or OpenAI.
package/SECURITY.md ADDED
@@ -0,0 +1,21 @@
1
+ # Security policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Use GitHub's private
6
+ [security advisory form](https://github.com/sergii-ziborov/granttap-mcp/security/advisories/new).
7
+ For urgent coordination, contact `sergii.ziborov@gmail.com`.
8
+
9
+ Do not open a public issue containing pairing codes, device keys, room
10
+ identifiers, real prompts, command payloads, local configuration, or a working
11
+ exploit. Include the affected package version or commit, expected impact, and
12
+ a minimal reproduction using synthetic data.
13
+
14
+ ## Local secrets
15
+
16
+ Pairing state belongs in `~/.granttap/` and must never be committed, attached
17
+ to an issue, or pasted into logs. The package avoids printing device secret
18
+ keys. Existing configuration files are backed up before hook installation.
19
+
20
+ Relay vulnerabilities should be reported through the
21
+ [GrantTap relay advisory form](https://github.com/sergii-ziborov/granttap-relay/security/advisories/new).
@@ -0,0 +1,109 @@
1
+ import QRCode from "qrcode";
2
+ import { generatePairingCode, sealWithCode } from "../../../../packages/core/crypto";
3
+ import { installClaudeHook, installCodexHook, type InstallResult } from "../install";
4
+ import {
5
+ createPairing,
6
+ machineConfigPath,
7
+ pairingUri,
8
+ phonePairingPath,
9
+ saveConfig,
10
+ } from "../config";
11
+
12
+ const DEFAULT_RELAY = "wss://granttap-relay.sergii-ziborov.workers.dev";
13
+
14
+ function hookLine(name: string, result: InstallResult): string {
15
+ switch (result.status) {
16
+ case "installed":
17
+ return ` ✓ ${name}: hook installed (${result.detail}; backup: .bak-granttap)`;
18
+ case "already":
19
+ return ` ✓ ${name}: hook already installed (${result.detail})`;
20
+ case "manual":
21
+ return ` ! ${name}: ${result.detail}`;
22
+ }
23
+ }
24
+
25
+ async function connect(relayUrl: string): Promise<void> {
26
+ const { machineCfg, phoneCfg } = createPairing(relayUrl);
27
+ saveConfig(machineConfigPath(), machineCfg);
28
+ saveConfig(phonePairingPath(), phoneCfg);
29
+
30
+ const uri = pairingUri(phoneCfg);
31
+ const qr = await QRCode.toString(uri, {
32
+ type: "terminal",
33
+ small: true,
34
+ errorCorrectionLevel: "L",
35
+ });
36
+ process.stdout.write(["", " Scan this code in GrantTap on iPhone:", "", qr].join("\n"));
37
+
38
+ const code = generatePairingCode(8);
39
+ const sealed = sealWithCode(phoneCfg, code);
40
+ const httpBase = relayUrl.replace(/^ws:/, "http:").replace(/^wss:/, "https:");
41
+ let codeReady = false;
42
+ try {
43
+ const response = await fetch(`${httpBase}/pair/${code}`, {
44
+ method: "PUT",
45
+ headers: { "content-type": "application/json" },
46
+ body: JSON.stringify(sealed),
47
+ });
48
+ codeReady = response.ok;
49
+ } catch {
50
+ codeReady = false;
51
+ }
52
+
53
+ process.stdout.write(
54
+ codeReady
55
+ ? [
56
+ "",
57
+ " Or enter this one-time code in the app:",
58
+ "",
59
+ ` ${code.slice(0, 4)} ${code.slice(4)}`,
60
+ "",
61
+ ` Relay: ${httpBase}`,
62
+ " The code expires after 15 minutes and can be used once.",
63
+ "",
64
+ ].join("\n")
65
+ : [
66
+ "",
67
+ ` ! The relay at ${httpBase} did not accept a short code.`,
68
+ " The QR still works. Check the relay and retry for code pairing.",
69
+ "",
70
+ ].join("\n"),
71
+ );
72
+
73
+ const skipHooks = process.env.GRANTTAP_SKIP_HOOKS === "1";
74
+ const claude = skipHooks ? null : installClaudeHook();
75
+ const codex = skipHooks ? null : installCodexHook();
76
+ process.stdout.write(
77
+ [
78
+ "",
79
+ " GrantTap is paired on this machine.",
80
+ "",
81
+ ` room: ${machineCfg.room}`,
82
+ ` relay: ${relayUrl}`,
83
+ ` machine cfg: ${machineConfigPath()}`,
84
+ ` phone cfg: ${phonePairingPath()} (manual fallback; keep private)`,
85
+ "",
86
+ ...(skipHooks
87
+ ? [" Hooks: skipped (GRANTTAP_SKIP_HOOKS=1)"]
88
+ : [hookLine("Claude Code", claude!), hookLine("Codex", codex!)]),
89
+ "",
90
+ " Keep the MCP server configured in your agent. Approval requests now",
91
+ " use the paired phone and watch; if the relay is unavailable, hooks fall",
92
+ " back to the agent's normal local approval flow.",
93
+ "",
94
+ ].join("\n"),
95
+ );
96
+ }
97
+
98
+ const relayUrl =
99
+ process.argv[2] ??
100
+ process.env.GRANTTAP_RELAY_URL ??
101
+ process.env.NODVOX_RELAY_URL ??
102
+ DEFAULT_RELAY;
103
+
104
+ void connect(relayUrl).catch((error: unknown) => {
105
+ process.stderr.write(
106
+ `[granttap-mcp] connect failed: ${error instanceof Error ? error.message : String(error)}\n`,
107
+ );
108
+ process.exitCode = 1;
109
+ });
@@ -11,6 +11,8 @@ if (command === "mcp") {
11
11
  entry = join(root, "apps", "mcp", "src", "server.ts");
12
12
  } else if (command === "setup") {
13
13
  entry = join(root, "apps", "bridge", "src", "bin", "setup.ts");
14
+ } else if (command === "connect") {
15
+ entry = join(root, "apps", "bridge", "src", "bin", "connect.ts");
14
16
  } else if (command === "hook" && (argument === "claude" || argument === "codex")) {
15
17
  entry = join(root, "apps", "bridge", "src", "bin", `${argument}-hook.ts`);
16
18
  } else {
@@ -18,6 +20,7 @@ if (command === "mcp") {
18
20
  [
19
21
  "Usage:",
20
22
  " granttap-mcp Start the MCP stdio server",
23
+ " granttap-mcp connect [url] Pair this machine with iPhone/Apple Watch",
21
24
  " granttap-mcp setup Install Claude Code and Codex approval hooks",
22
25
  " granttap-mcp hook <agent> Internal hook entry point",
23
26
  "",
Binary file
Binary file
package/package.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "name": "granttap-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "E2EE MCP bridge and approval hooks for GrantTap on iPhone and Apple Watch.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "granttap-mcp": "./bin/granttap-mcp.mjs"
7
+ "granttap-mcp": "bin/granttap-mcp.mjs"
8
8
  },
9
9
  "files": [
10
10
  "apps",
11
11
  "bin",
12
+ "docs",
12
13
  "packages",
13
14
  "LICENSE",
14
- "README.md"
15
+ "README.md",
16
+ "SECURITY.md"
15
17
  ],
16
18
  "scripts": {
17
19
  "start": "node bin/granttap-mcp.mjs",
@@ -22,6 +24,7 @@
22
24
  },
23
25
  "dependencies": {
24
26
  "@modelcontextprotocol/sdk": "^1.29.0",
27
+ "qrcode": "^1.5.4",
25
28
  "tsx": "^4.19.2",
26
29
  "tweetnacl": "^1.0.3",
27
30
  "ws": "^8.18.0",
@@ -29,6 +32,7 @@
29
32
  },
30
33
  "devDependencies": {
31
34
  "@types/node": "^22.10.2",
35
+ "@types/qrcode": "^1.5.6",
32
36
  "@types/ws": "^8.5.13",
33
37
  "typescript": "^5.7.2"
34
38
  },