granttap-mcp 0.6.7 → 0.6.10

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.
Files changed (97) hide show
  1. package/README.md +97 -251
  2. package/SECURITY.md +43 -69
  3. package/apps/bridge/src/approval-state.ts +7 -5
  4. package/apps/bridge/src/approval.ts +4 -67
  5. package/apps/bridge/src/bin/claude-hook.ts +18 -0
  6. package/apps/bridge/src/bin/connect.ts +27 -6
  7. package/apps/bridge/src/bin/reset.ts +53 -0
  8. package/apps/bridge/src/bin/setup.ts +87 -17
  9. package/apps/bridge/src/capabilities/README.md +3 -0
  10. package/apps/bridge/src/capabilities/descriptors.ts +91 -0
  11. package/apps/bridge/src/capabilities/metadata.ts +220 -0
  12. package/apps/bridge/src/capabilities/skills.ts +68 -0
  13. package/apps/bridge/src/capabilities/types.ts +34 -0
  14. package/apps/bridge/src/capabilities.ts +8 -440
  15. package/apps/bridge/src/config/README.md +7 -0
  16. package/apps/bridge/src/config/capability-policy.ts +160 -0
  17. package/apps/bridge/src/config/pairing.ts +143 -0
  18. package/apps/bridge/src/config/paths.ts +31 -0
  19. package/apps/bridge/src/config/runtime.ts +142 -0
  20. package/apps/bridge/src/config.ts +8 -524
  21. package/apps/bridge/src/install.ts +6 -2
  22. package/apps/bridge/src/monitor-heartbeat.ts +27 -0
  23. package/apps/bridge/src/monitor-publish-loop.ts +29 -0
  24. package/apps/bridge/src/monitor-session-activity.ts +60 -0
  25. package/apps/bridge/src/monitor-single-flight.ts +32 -0
  26. package/apps/bridge/src/monitor.ts +58 -72
  27. package/apps/bridge/src/pairing.ts +26 -0
  28. package/apps/bridge/src/reply/README.md +3 -0
  29. package/apps/bridge/src/reply/attachments.ts +50 -0
  30. package/apps/bridge/src/reply/process.ts +55 -0
  31. package/apps/bridge/src/reply/routing.ts +18 -0
  32. package/apps/bridge/src/reply/types.ts +8 -0
  33. package/apps/bridge/src/reply.ts +6 -215
  34. package/apps/bridge/src/sessions/claude.ts +4 -1
  35. package/apps/bridge/src/sessions/codex.ts +3 -0
  36. package/apps/bridge/src/sessions/copilot-capabilities.ts +127 -0
  37. package/apps/bridge/src/sessions/copilot.ts +1 -129
  38. package/apps/bridge/src/sessions/cursor/README.md +6 -0
  39. package/apps/bridge/src/sessions/cursor/activity.ts +97 -0
  40. package/apps/bridge/src/sessions/cursor/catalog.ts +187 -0
  41. package/apps/bridge/src/sessions/cursor/scan.ts +162 -0
  42. package/apps/bridge/src/sessions/cursor/transcripts.ts +257 -0
  43. package/apps/bridge/src/sessions/cursor.ts +9 -868
  44. package/apps/bridge/src/sessions/telemetry/README.md +6 -0
  45. package/apps/bridge/src/sessions/telemetry/command-preview.ts +105 -0
  46. package/apps/bridge/src/sessions/telemetry/estimation.ts +117 -0
  47. package/apps/bridge/src/sessions/telemetry/identity.ts +53 -0
  48. package/apps/bridge/src/sessions/telemetry.ts +43 -348
  49. package/apps/mcp/src/bin/status.ts +11 -9
  50. package/apps/mcp/src/create-server.ts +8 -333
  51. package/apps/mcp/src/cursor-config.ts +3 -3
  52. package/apps/mcp/src/http-service/README.md +3 -0
  53. package/apps/mcp/src/http-service/common.ts +48 -0
  54. package/apps/mcp/src/http-service/health.ts +56 -0
  55. package/apps/mcp/src/http-service/installer.ts +130 -0
  56. package/apps/mcp/src/http-service/snapshot.ts +125 -0
  57. package/apps/mcp/src/http-service.ts +15 -396
  58. package/apps/mcp/src/mcp-tools/README.md +6 -0
  59. package/apps/mcp/src/mcp-tools/connect.ts +75 -0
  60. package/apps/mcp/src/mcp-tools/interaction.ts +62 -0
  61. package/apps/mcp/src/mcp-tools/relay.ts +161 -0
  62. package/apps/mcp/src/oauth/README.md +3 -0
  63. package/apps/mcp/src/oauth/consent-page.ts +58 -0
  64. package/apps/mcp/src/oauth/store.ts +66 -0
  65. package/apps/mcp/src/oauth-provider.ts +9 -242
  66. package/apps/mcp/src/provider-status.ts +4 -66
  67. package/apps/mcp/src/server.ts +1 -1
  68. package/bin/granttap-mcp.mjs +25 -24
  69. package/cursor-plugin/README.md +4 -4
  70. package/cursor-plugin/commands/connect.md +1 -1
  71. package/cursor-plugin/skills/connect/SKILL.md +4 -4
  72. package/docs/cursor-authorize.md +16 -59
  73. package/docs/images/apple-watch-approval.png +0 -0
  74. package/docs/images/apple-watch-inbox.png +0 -0
  75. package/docs/images/apple-watch-task.png +0 -0
  76. package/docs/images/iphone-chat.png +0 -0
  77. package/docs/images/iphone-claude-tasks.png +0 -0
  78. package/docs/images/iphone-command-center.png +0 -0
  79. package/docs/images/iphone-mcp-usage.png +0 -0
  80. package/docs/images/iphone-photo-preview.png +0 -0
  81. package/docs/images/iphone-task-detail.png +0 -0
  82. package/package.json +12 -5
  83. package/packages/core/relay-client-types.ts +38 -0
  84. package/packages/core/relay-client.ts +7 -44
  85. package/packages/core/relay-envelope.ts +37 -0
  86. package/packages/protocol/messages/README.md +6 -0
  87. package/packages/protocol/messages/approvals.ts +87 -0
  88. package/packages/protocol/messages/capabilities.ts +104 -0
  89. package/packages/protocol/messages/interaction.ts +68 -0
  90. package/packages/protocol/messages/primitives.ts +24 -0
  91. package/packages/protocol/messages/sessions.ts +169 -0
  92. package/packages/protocol/schema.ts +61 -614
  93. package/apps/bridge/src/bin/web.ts +0 -33
  94. package/apps/bridge/src/cloud-approvals.ts +0 -324
  95. package/apps/bridge/src/scheduler.ts +0 -298
  96. package/docs/images/phone-activity.png +0 -0
  97. package/docs/images/watch-approval.png +0 -0
package/README.md CHANGED
@@ -2,303 +2,149 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/granttap-mcp)](https://www.npmjs.com/package/granttap-mcp)
4
4
  [![CI](https://github.com/sergii-ziborov/granttap-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/sergii-ziborov/granttap-mcp/actions/workflows/ci.yml)
5
- [![Node.js 20+](https://img.shields.io/badge/node-%3E%3D20-339933?logo=node.js&logoColor=white)](package.json)
6
5
  [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
6
 
8
- **Keep Codex and Claude Code moving from your iPhone or Apple Watch.**
7
+ GrantTap is a Personal live control center for local coding agents.
9
8
 
10
- Approve commands, follow active tasks, reply, attach files, start new work, and
11
- run local schedules without exposing agent traffic to the relay. GrantTap MCP is
12
- the open-source machine-side bridge: the agents stay on your Mac, and Cloudflare
13
- only routes authenticated ciphertext it cannot decrypt.
9
+ > See what your coding agents are doing. Step in when they need you.
14
10
 
15
- [Website](https://granttap.com) ·
16
- [npm](https://www.npmjs.com/package/granttap-mcp) ·
11
+ This repository is the canonical machine runtime: CLI, MCP server, provider
12
+ hooks, local adapters, and TypeScript wire schemas. Agents and provider
13
+ credentials stay on your computer. Native iPhone and Apple Watch traffic is
14
+ end-to-end encrypted.
15
+
16
+ [Website](https://granttap.com) · [npm](https://www.npmjs.com/package/granttap-mcp) ·
17
17
  [Security model](SECURITY.md) ·
18
- [Self-hostable relay](https://github.com/sergii-ziborov/granttap-relay)
18
+ [Relay source](https://github.com/sergii-ziborov/granttap-relay)
19
+
20
+ ## iPhone and Apple Watch
19
21
 
20
22
  <p align="center">
21
- <img src="https://raw.githubusercontent.com/sergii-ziborov/granttap-mcp/main/docs/images/iphone-command-center.png" alt="GrantTap command center on iPhone showing a Codex approval, an agent question, task search, agent switcher, chat history, and MCP usage" width="330">
22
- &nbsp;&nbsp;&nbsp;
23
- <img src="https://raw.githubusercontent.com/sergii-ziborov/granttap-mcp/main/docs/images/apple-watch-approval.png" alt="GrantTap Codex approval on Apple Watch" width="230">
23
+ <img src="docs/images/iphone-command-center.png" width="230" alt="GrantTap Now with Needs You and at-risk tasks">
24
+ <img src="docs/images/iphone-chat.png" width="230" alt="GrantTap live task timeline and composer">
25
+ <img src="docs/images/iphone-mcp-usage.png" width="230" alt="GrantTap actionable usage overview">
24
26
  </p>
25
27
 
26
- <p align="center"><sub>Current iPhone and Apple Watch UI captured from the app's clearly labelled demo mode — no concept renders.</sub></p>
27
-
28
- The companion iPhone and Apple Watch app is currently in testing. Release
29
- status is published at [granttap.com](https://granttap.com).
28
+ <p align="center">
29
+ <img src="docs/images/apple-watch-inbox.png" width="180" alt="GrantTap Needs You on Apple Watch">
30
+ <img src="docs/images/apple-watch-approval.png" width="180" alt="GrantTap approval on Apple Watch">
31
+ </p>
30
32
 
31
- ## What you get
33
+ ## Supported providers
32
34
 
33
- - **Real approvals away from the Mac.** Claude Code or Codex pauses at its
34
- permission hook; Allow or Deny returns to that same agent flow.
35
- - **One task view for both agents.** See recent Codex and Claude Code tasks,
36
- human-readable activity, delivery state, usage, and context data the agent
37
- actually exposes.
38
- - **Continue work from the phone.** Reply to an existing task or create a new
39
- one for the selected agent and advertised workspace. Send up to five photos,
40
- camera images, or documents in one message.
41
- - **A useful Watch app, not an approval-only notification.** Browse tasks, open
42
- recent activity, approve a command, or reply by voice or text.
43
- - **Per-task controls that are enforced locally.** Inspect MCP servers and
44
- repository skills, disable MCP servers for later GrantTap-delivered turns,
45
- choose a relevant MCP or skill, and select Codex sandbox access.
46
- - **A local scheduler, not a pretend provider API.** Create recurring Codex or
47
- Claude Code runs manually or through a conversational planner, then inspect
48
- the run history from the phone.
49
- - **Receipts instead of optimistic UI.** Messages have stable IDs, encrypted
50
- accepted/rejected receipts, bounded retries, and queued/sending/delivered/
51
- failed state.
35
+ - Primary: Claude Code and Codex.
36
+ - Beta: Cursor.
37
+ - Experimental where available: GitHub Copilot CLI and Grok Build.
52
38
 
53
- ## See it in action
39
+ GrantTap reports the depth each provider actually exposes. Visibility does not
40
+ imply deterministic remote blocking or full mobile continuation.
54
41
 
55
- ### iPhone
42
+ ## Install
56
43
 
57
- | Command center | Codex task controls | Observed MCP usage | Claude Code and scheduler |
58
- | --- | --- | --- | --- |
59
- | <img src="https://raw.githubusercontent.com/sergii-ziborov/granttap-mcp/main/docs/images/iphone-command-center.png" alt="GrantTap iPhone command center" width="210"> | <img src="https://raw.githubusercontent.com/sergii-ziborov/granttap-mcp/main/docs/images/iphone-task-detail.png" alt="Codex task context, MCP permissions, and sandbox access in GrantTap" width="210"> | <img src="https://raw.githubusercontent.com/sergii-ziborov/granttap-mcp/main/docs/images/iphone-mcp-usage.png" alt="Observed MCP and skill usage on iPhone" width="210"> | <img src="https://raw.githubusercontent.com/sergii-ziborov/granttap-mcp/main/docs/images/iphone-claude-tasks.png" alt="Claude Code task list and conversational scheduler on iPhone" width="210"> |
44
+ ```bash
45
+ npm install -g granttap-mcp
46
+ granttap setup
47
+ ```
60
48
 
61
- The Codex task screen exposes the context window, supported compaction, MCP
62
- allow/deny state, and sandbox access. Claude Code gets its own visual treatment
63
- and planner while keeping its different permission and compaction limits clear.
64
- MCP usage counts only observed calls; context figures are labelled estimates,
65
- not separate MCP billing.
49
+ `granttap setup` detects supported local agents, installs or repairs their
50
+ hooks, installs the background helper, configures Cursor's persistent local
51
+ OAuth service when Cursor is present, and starts phone pairing when run in an
52
+ interactive terminal. It ends with one exact next action.
66
53
 
67
- ### Apple Watch
54
+ The normal CLI surface is intentionally small:
68
55
 
69
- | Task inbox | Recent activity and reply | Command approval |
70
- | --- | --- | --- |
71
- | <img src="https://raw.githubusercontent.com/sergii-ziborov/granttap-mcp/main/docs/images/apple-watch-inbox.png" alt="GrantTap task and approval inbox on Apple Watch" width="230"> | <img src="https://raw.githubusercontent.com/sergii-ziborov/granttap-mcp/main/docs/images/apple-watch-task.png" alt="GrantTap task activity with voice and text reply on Apple Watch" width="230"> | <img src="https://raw.githubusercontent.com/sergii-ziborov/granttap-mcp/main/docs/images/apple-watch-approval.png" alt="Codex command approval on Apple Watch" width="230"> |
56
+ ```text
57
+ granttap setup
58
+ granttap status [--json]
59
+ granttap connect [--relay <wss-url>]
60
+ granttap reset [--yes]
61
+ ```
72
62
 
73
- ## Connect in under a minute
63
+ `connect` reuses a valid pairing. If none exists, it creates a one-time QR.
64
+ Custom relays are CLI-only and explicit. `reset` moves active pairing files to
65
+ recoverable local backups before a new pairing can be created.
74
66
 
75
- Add GrantTap to each agent you use:
67
+ Cursor setup is automatic in the normal flow. The advanced repair command is:
76
68
 
77
69
  ```bash
78
- npm install
79
- npm install -g .
80
- codex mcp add granttap -- granttap
81
- claude mcp add granttap -- granttap
70
+ granttap cursor repair
82
71
  ```
83
72
 
84
- Those commands use this checkout. Publish/release it before replacing the local
85
- install with an npm registry version.
73
+ After Codex hooks are installed, open `/hooks`, review and trust both exact
74
+ GrantTap hooks, then restart Codex. GrantTap never treats installation as user
75
+ trust.
86
76
 
87
- Start a fresh agent task and say **“Connect GrantTap.”** The `connect` tool:
77
+ ## MCP contract
88
78
 
89
- 1. creates a new end-to-end encrypted pairing;
90
- 2. returns a scannable one-time QR directly in the agent chat.
79
+ `tools/list` returns exactly four public tools:
91
80
 
92
- Then run the separate MCP `setup` tool. It installs Cursor shell/MCP policy
93
- hooks, the complete Claude Code matcher, both Codex hooks, and the per-user
94
- background helper for task sync and schedules.
95
-
96
- Scan the QR with GrantTap on iPhone. No terminal QR, copied pairing JSON, or
97
- open background terminal is required.
81
+ | Tool | Contract |
82
+ | --- | --- |
83
+ | `connect` | Reuse the existing production pairing or return a one-time QR |
84
+ | `notify` | Send a non-blocking status update of at most 2,000 characters |
85
+ | `ask_yes_no` | Ask a yes/no question and wait for the explicit answer |
86
+ | `ask` | Ask an open question and wait for typed or spoken text |
98
87
 
99
- If an MCP client cannot render image content, use the CLI fallback:
88
+ MCP `connect` accepts no custom routing, replacement, or key-rotation input.
89
+ Setup is CLI-only because it changes provider configuration and must not be
90
+ available to a model through prompt injection.
100
91
 
101
- ```bash
102
- npm install -g granttap-mcp
103
- granttap connect
104
- granttap setup
105
- granttap status
106
- ```
92
+ Provider-native approvals and mobile continuation require the matching local
93
+ adapter. MCP registration alone is never reported as proof that an integration
94
+ is ready.
107
95
 
108
- CLI `connect` prints a one-time QR and short manual code. `setup` is idempotent:
109
- it preserves unrelated agent settings and backs up a configuration file before
110
- changing it. The pairing is stored locally in `~/.granttap/machine.json`.
111
- Existing beta state under `~/.nodvox/` is migrated automatically.
96
+ ## What the runtime publishes
112
97
 
113
- After setup, open `/hooks` in Codex, review and trust both exact GrantTap hooks,
114
- then restart Codex. Installation alone is not reported as trusted or connected.
98
+ The bounded encrypted protocol preserves:
115
99
 
116
- ## Cursor Settings Authorize
100
+ - provider, task, computer, model, workspace, branch, state, and summary;
101
+ - visible activity, delivery state, context and token counters;
102
+ - MCP, Skill, and CLI observations;
103
+ - child-agent relationships;
104
+ - per-capability outcome: `success`, `error`, `cancelled`, or `unknown`.
117
105
 
118
- Cursor only shows **Authorize / Sign in** for **HTTP/SSE** MCP servers that
119
- speak OAuth (same pattern as Lovable / Figma). The default stdio entry
120
- (`command` + `args`) cannot show that button — Cursor documents stdio auth as
121
- **Manual**.
106
+ An optional bounded `errorClass` may describe an error category. Full tool
107
+ error payloads are not copied into usage telemetry by default.
122
108
 
123
- To enable Authorize for GrantTap, use the one-step local setup:
109
+ ## Local enforcement
124
110
 
125
- ```bash
126
- npm install -g granttap-mcp
127
- granttap authorize
128
- ```
111
+ GrantTap can narrow later actions for an exact task only where a provider
112
+ offers a deterministic local hook. Global provider configuration always wins.
113
+ Read-only integrations stay read-only in the app instead of presenting a fake
114
+ toggle.
129
115
 
130
- It preserves unrelated entries in `~/.cursor/mcp.json`, replaces only the
131
- `granttap` entry with the loopback HTTP endpoint, keeps a one-time
132
- `.bak-granttap`, and starts the OAuth MCP server. Then open
133
- **Cursor Settings → MCP → GrantTap → Authorize**. The local consent page shows
134
- the pairing QR plus a manual-token fallback when the Mac is not paired yet.
135
- `granttap authorize` installs a loopback-only per-user LaunchAgent, verifies its
136
- exact `/healthz` identity, writes Cursor config only after that check succeeds,
137
- and exits. The service uses RunAtLoad + KeepAlive, so closing the terminal or
138
- restarting the Mac does not leave Cursor pointing at a dead URL.
116
+ The user-facing approval modes map to the existing runtime policy:
139
117
 
140
- The foreground troubleshooting flow is:
118
+ | Personal UI | Runtime |
119
+ | --- | --- |
120
+ | Ask for risky actions | `except_push` |
121
+ | Ask for every action | `ask` |
122
+ | Use agent defaults | no GrantTap gate for that task |
141
123
 
142
- 1. Run the local HTTP OAuth server (loopback only):
124
+ Legacy custom levels remain compatible but are not part of the primary flow.
143
125
 
144
- ```bash
145
- granttap serve
146
- # listens on http://127.0.0.1:17342/mcp
147
- ```
126
+ ## Relay boundary
148
127
 
149
- 2. Point Cursor at the HTTP URL in `~/.cursor/mcp.json` (replace the stdio
150
- `command` entry):
151
-
152
- ```json
153
- {
154
- "mcpServers": {
155
- "granttap": {
156
- "url": "http://127.0.0.1:17342/mcp"
157
- }
158
- }
159
- }
160
- ```
128
+ Pairing and task keys are generated locally. The relay receives opaque routing
129
+ metadata and ciphertext, not provider credentials or task plaintext. Pairing
130
+ handoff uses a relay-visible random mailbox ID plus an independent transfer key
131
+ that stays in the QR.
161
132
 
162
- 3. Open **Cursor Settings MCP GrantTap** and click **Authorize**.
163
- A local browser page confirms linking Cursor to this Mac’s pairing
164
- (`~/.granttap`). If unpaired, it shows a one-time QR first.
165
-
166
- OAuth tokens are stored in `~/.granttap/mcp-oauth.json` (mode `0600`). They do
167
- **not** replace E2EE pairing keys. Claude Code / Codex can keep using the
168
- stdio transport; Authorize is a Cursor Settings affordance.
169
-
170
- Details: [docs/cursor-authorize.md](docs/cursor-authorize.md).
171
-
172
- `granttap setup` is the policy-hook entry point: it installs Cursor
173
- `beforeShellExecution`, `afterShellExecution`, and `beforeMCPExecution` hooks,
174
- plus Claude/Codex hooks and background task sync. It does not opt a new user
175
- into OAuth; when an exact Cursor HTTP entry already exists, it also repairs the
176
- persistent OAuth service. Per-chat blocks are checked before phone routing,
177
- and ambiguous/unscoped Cursor calls fall back to Cursor's native permission UI.
178
-
179
- ## Codex and Claude Code: honest capability matrix
180
-
181
- | Capability | Codex | Claude Code |
182
- | --- | --- | --- |
183
- | Approval hook | `PermissionRequest` when Codex hooks are enabled | `PreToolUse` |
184
- | Resume an existing task | Yes, through the local Codex CLI | Yes, through the local Claude CLI |
185
- | Start a new persistent task | Yes | Yes |
186
- | Up to five attachments within one encrypted-frame budget | Images through Codex image inputs; documents as local paths | Local image/document paths in the turn |
187
- | Change filesystem access from iPhone | Read-only, workspace, or full for the next GrantTap turn | Not exposed; the existing Claude policy remains authoritative |
188
- | Disable MCP per task | Enforced for later GrantTap-delivered turns | Enforced for later GrantTap-delivered turns |
189
- | Usage and context | Reported when present in local task logs | Reported when present in local task logs |
190
- | Trigger real context compaction | Yes, for an idle task through Codex app-server | No supported remote API; GrantTap reports this honestly |
191
- | Conversational schedule planner | Ephemeral read-only run | Ephemeral plan-mode run |
192
-
193
- GrantTap controls local Codex and Claude Code tasks. It does **not** claim to
194
- create ordinary ChatGPT chats, private ChatGPT Scheduled Tasks, Codex
195
- Automations, or Claude Routines through unpublished provider APIs.
196
-
197
- Globally disabled MCP servers stay disabled. A phone choice only narrows later
198
- turns sent through GrantTap; it cannot broaden the agent's global configuration.
199
- Repository skills are discovered only in the selected task workspace under
200
- `.agents/skills` or `.claude/skills`.
201
-
202
- ## MCP tools
203
-
204
- | Tool | Result |
205
- | --- | --- |
206
- | `connect` | Creates a pairing and returns a secure one-time QR in chat |
207
- | `ask` | Sends an open question and waits for a spoken or typed reply |
208
- | `ask_yes_no` | Sends a yes/no question and waits for a tap |
209
- | `notify` | Sends a non-blocking status update |
210
- | `setup` | Registers Cursor/Claude/Codex policy hooks and the terminal-free helper |
211
-
212
- The default answer timeout is three minutes. Override it with
213
- `GRANTTAP_ASK_TIMEOUT_MS`.
214
-
215
- ## Why the relay cannot read a session
216
-
217
- - Endpoint keys are generated locally. The relay never receives a device's
218
- secret encryption key.
219
- - Pairing hand-off uses an opaque mailbox ID plus an independent 256-bit
220
- transfer key. Only the mailbox ID reaches Cloudflare; the key stays in the QR
221
- or manual token.
222
- - Every attached Codex or Claude Code task receives its own random 256-bit task
223
- key. Disclosure of one task key cannot decrypt another task.
224
- - Questions, commands, replies, attachments, approvals, scheduler drafts, and
225
- task activity remain authenticated ciphertext across the network, relay,
226
- Durable Objects, and APNs path.
227
- - APNs carries only a content-neutral wake. It contains no title, prompt,
228
- command, path, task kind, or response.
229
- - Cloudflare accepts at most a 32 MiB WebSocket frame. Because task messages are
230
- sealed and base64-encoded twice, attachments share a 16,000,000-character
231
- base64 budget (about 12 MB raw total); five small files fit, five 6 MB files do not.
232
-
233
- The in-chat QR is marked user-only for MCP hosts, but that annotation is not a
234
- cryptographic separation from the model provider. Use CLI `connect` when the
235
- model provider itself is part of your threat model; Cloudflare still receives
236
- only the mailbox id and ciphertext in either flow.
237
-
238
- The relay can still observe operational metadata: opaque room/mailbox IDs, IP
239
- addresses, timing, ciphertext sizes, and APNs device token/environment. A
240
- compromised authorized endpoint can read the tasks explicitly granted to that
241
- endpoint; cryptography cannot hide plaintext from a device authorized to show
242
- it. The exact boundary and threat-model limits are documented in
243
- [SECURITY.md](SECURITY.md).
244
-
245
- ## Task sync and scheduling
246
-
247
- The background helper publishes a bounded window of recent local Codex and
248
- Claude Code task metadata. Older chat metadata is available separately for up
249
- to 90 days and 160 chats; full activity for a task is sent only after the phone
250
- subscribes to it. Hidden reasoning is never converted into visible activity.
251
-
252
- New phone-created tasks default to an isolated per-agent GrantTap workspace.
253
- The phone can instead select a same-agent folder already advertised by a recent
254
- local task. The helper rejects arbitrary unadvertised paths.
255
-
256
- Schedules use standard five-field cron in the Mac's timezone. The app supports
257
- hourly, daily, weekday, selected-weekday, and monthly series, plus enable,
258
- disable, edit, delete, and run-now actions. Every run records local start/end
259
- time, agent, status, result, and created task ID.
260
-
261
- ## CLI commands
262
-
263
- | Command | Purpose |
264
- | --- | --- |
265
- | *(no command)* | Starts the GrantTap MCP stdio server |
266
- | `serve` | HTTP MCP + loopback OAuth for Cursor Settings → Authorize |
267
- | `authorize` | Installs the persistent loopback OAuth service, verifies health, and configures Cursor |
268
- | `connect [relayUrl]` | Creates an E2EE pairing; optionally targets a self-hosted `wss://` relay |
269
- | `setup` | Registers Cursor/Claude/Codex hooks, background sync, and repairs configured OAuth |
270
- | `status [--json]` | Reads local readiness; JSON uses `granttap.provider-status.v1` and contains no keys |
271
-
272
- The installed public command is `granttap`; `granttap-mcp` remains an alias for
273
- existing scripts. `status` only reads configuration and runtime state—it does
274
- not install hooks, pair a device, start OAuth, or reload the background helper.
275
- If Cursor has an HTTP GrantTap entry, status requires both an owned persistent
276
- service and a live identity-checked health response; a dead URL is never shown
277
- as connected. Without an HTTP entry, OAuth remains optional and Cursor policy
278
- readiness is based on the full hook set, pairing, and background sync.
133
+ APNs carries a content-neutral wake only. It contains no prompt, task title,
134
+ command, path, request ID, or ciphertext. See [SECURITY.md](SECURITY.md) for the
135
+ complete boundary and reporting instructions.
279
136
 
280
137
  ## Development
281
138
 
282
- Requires Node.js 20 or newer.
283
-
284
139
  ```bash
285
- git clone https://github.com/sergii-ziborov/granttap-mcp.git
286
- cd granttap-mcp
287
140
  npm install
288
- npm test
289
141
  npm run typecheck
142
+ npm test
143
+ npm run package:allowlist
290
144
  ```
291
145
 
292
- Start the stdio server with `npm start`. Run `npm run setup` only on a machine
293
- where you want GrantTap hooks installed.
294
-
295
- ## Links
296
-
297
- - [GrantTap product site](https://granttap.com)
298
- - [granttap-mcp on npm](https://www.npmjs.com/package/granttap-mcp)
299
- - [Public relay source](https://github.com/sergii-ziborov/granttap-relay)
300
- - [Privacy](https://granttap.com/privacy)
301
- - [Support](https://granttap.com/support)
302
- - [Security policy](SECURITY.md)
146
+ Do not publish from a dirty checkout or before the package allowlist, tests,
147
+ typecheck, and release checks pass.
303
148
 
304
- GrantTap is not affiliated with Anthropic or OpenAI.
149
+ GrantTap is not affiliated with Anthropic, OpenAI, Apple, Microsoft, Anysphere,
150
+ GitHub, or xAI.
package/SECURITY.md CHANGED
@@ -6,82 +6,56 @@ Use GitHub's private
6
6
  [security advisory form](https://github.com/sergii-ziborov/granttap-mcp/security/advisories/new).
7
7
  For urgent coordination, contact `sergii.ziborov@gmail.com`.
8
8
 
9
- Do not open a public issue containing pairing tokens, 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.
9
+ Do not open a public issue containing pairing tokens, device keys, room IDs,
10
+ real prompts, command payloads, APNs tokens, local configuration, or a working
11
+ exploit. Use synthetic data and include the affected package version or commit.
13
12
 
14
13
  ## Local secrets
15
14
 
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.
15
+ Pairing state belongs in `~/.granttap/`. It must never be committed, attached
16
+ to an issue, or printed in logs. The active secret classes are endpoint NaCl
17
+ keys, per-task keys, the one-time pairing transfer key, and the random room
18
+ credential used for authenticated WebSocket and APNs registration.
19
19
 
20
- Current pairings contain three independent secret classes: NaCl endpoint keys,
21
- random per-task keys, and a random `pushAuth` room credential that authorizes
22
- the room WebSocket and APNs device-token registration at the relay. None may be logged.
23
- The relay receives only a hash of `pushAuth` and never receives an endpoint's
24
- secret encryption key.
20
+ Provider credentials remain in each provider's own local authentication flow.
21
+ GrantTap does not send them to the relay.
25
22
 
26
23
  ## Cryptographic boundary
27
24
 
28
- - Every device pairing is generated locally and has unique Curve25519 secret
29
- keys. A device from another pairing cannot authenticate or decrypt it.
30
- - Pairing hand-off v2 uses a relay-visible random 128-bit mailbox id and a
31
- separate random 256-bit transfer key. Only the mailbox id is used in the HTTP
32
- path. The key stays in the QR/manual token, so a relay operator or a Durable
33
- Object database dump cannot open the parked pairing blob.
34
- - The terminal `connect` flow keeps that transfer key outside model context.
35
- The MCP `connect` tool deliberately returns a QR in chat for convenience and
36
- marks it user-only, but MCP audience annotations are not a cryptographic
37
- boundary: depending on the host, the chat/model provider may receive tool
38
- images. Use terminal pairing when the model provider is in your threat model.
39
- - Every attached Codex or Claude Code task receives a separate random 256-bit
40
- task key. Task messages, attachments, visible activity, access/MCP changes,
41
- compaction results, and task-bound approvals use this additional authenticated
42
- encryption layer. A key copied from one task cannot decrypt another task.
43
- - Task keys are delivered only inside the already authenticated device-to-device
44
- NaCl channel and are stored in device-only Keychain on iOS and a mode `0600`
45
- file on the agent Mac.
46
- - Plaintext exists only at an authorized endpoint. Every route between the Mac
47
- and iPhone remains authenticated ciphertext while it crosses the app
48
- transport, network, Cloudflare, Durable Objects, and APNs.
49
- - APNs is only a content-neutral wake. It contains no task kind, request id,
50
- delivery id, title, prompt, command, path, or response.
51
- It is a best-effort silent background notification; after waking, iOS pulls
52
- and decrypts the queue and creates the single actionable local notification.
53
- - Scheduler-planner turns and their structured drafts cross Cloudflare only as
54
- authenticated device-to-device ciphertext. The selected CLI runs locally in
55
- ephemeral read-only/plan mode; it cannot silently turn planning into a
56
- persistent task or workspace mutation.
57
-
58
- Cloudflare can observe service metadata required to operate the relay: an
59
- opaque room/mailbox id, routing role, timestamps/expiry, IP address, ciphertext
60
- size, APNs device token/environment, and a content-neutral wake flag. It cannot
61
- derive plaintext from those fields or from its stored database.
62
-
63
- The honest limit: a device can decrypt every task key that was explicitly
64
- granted to that device. Possession of a device authorized for task A alone
65
- cannot decrypt another pairing or task B unless B's independent key was also
66
- granted to it. Cryptography cannot both authorize a device for a task and
67
- prevent that same device from reading it. Compromise of the agent Mac is
68
- also outside the relay threat model because the Mac necessarily has the local
69
- agent transcripts and keys. The isolation guarantee is against Cloudflare,
70
- database/network compromise, other pairings/devices, and disclosure of a
71
- different task's key—not against an already authorized endpoint.
72
-
73
- The delivery ledger and scheduler history are stored only on the paired Mac.
74
- They contain random message ids and local task metadata and are bounded and
75
- expired; do not attach real copies to bug reports. Delivery ACK is emitted only
76
- after a consumer accepts the decrypted payload. A processing lease recovers
77
- after a crash; agent execution is at-least-once across that rare crash window,
78
- because third-party Codex/Claude CLIs do not expose a transactional idempotency key.
79
-
80
- Chat history and capability metadata follow the same cryptographic boundary.
81
- The bridge reads a bounded set of local Codex/Claude logs, emits only chat
82
- metadata and visible activity, and places it inside the existing device or
83
- independent per-task NaCl box. Structured MCP/skill fields describe observed
84
- tool calls; hidden reasoning is never turned into activity or usage records.
25
+ - Every computer pairing is generated locally with unique Curve25519 keys.
26
+ - Pairing handoff uses an opaque 128-bit mailbox ID and an independent 256-bit
27
+ transfer key. The relay receives the mailbox ID and ciphertext, not the key.
28
+ - Each attached task has an independent 256-bit task key. One task key cannot
29
+ decrypt another task.
30
+ - Task keys travel only inside the authenticated device channel and are stored
31
+ in device-protected storage.
32
+ - Native phone/watch payloads remain authenticated ciphertext across the
33
+ network, Cloudflare, Durable Objects, and the offline queue.
34
+ - APNs is a neutral wake. It contains no prompt, task title, command, path,
35
+ task/request/delivery ID, or task ciphertext.
36
+ - MCP audience annotations are not a cryptographic model-provider boundary.
37
+ Use CLI `granttap connect` when the model provider itself is in the threat
38
+ model.
39
+
40
+ Cloudflare can observe operational metadata required to run the relay: opaque
41
+ room/mailbox IDs, routing role, IP address, timing, expiry, ciphertext size, and
42
+ APNs token/environment. It cannot decrypt task content from those fields.
43
+
44
+ The honest endpoint limit remains: a compromised device can read the tasks
45
+ whose independent keys were granted to it. The protection is against relay,
46
+ database/network compromise, other pairings, and disclosure of a different
47
+ task key—not against an endpoint already authorized for that task.
48
+
49
+ ## Runtime controls
50
+
51
+ Provider hooks enforce policy on the computer. Global provider deny always
52
+ wins. If a provider cannot deterministically block a capability, GrantTap must
53
+ report the observation as read-only instead of implying enforcement.
54
+
55
+ `setup` is CLI-only because it writes local provider configuration and installs
56
+ background services. The public MCP server can only connect an existing or new
57
+ phone pairing, notify, and ask bounded questions; it cannot reconfigure the
58
+ machine or select a custom relay.
85
59
 
86
60
  Relay vulnerabilities should be reported through the
87
61
  [GrantTap relay advisory form](https://github.com/sergii-ziborov/granttap-relay/security/advisories/new).
@@ -20,11 +20,12 @@ import {
20
20
  } from "node:fs";
21
21
  import { join } from "node:path";
22
22
  import type { RelayClient } from "../../../packages/core/relay-client";
23
- import type {
24
- ApprovalDecision,
25
- ApprovalRequest,
26
- ApprovalResolved,
27
- ApprovalsStatus,
23
+ import {
24
+ approvalAction,
25
+ type ApprovalDecision,
26
+ type ApprovalRequest,
27
+ type ApprovalResolved,
28
+ type ApprovalsStatus,
28
29
  } from "../../../packages/protocol/schema";
29
30
  import { configDir } from "./config";
30
31
 
@@ -654,6 +655,7 @@ export function approvalsStatus(now = Date.now()): ApprovalsStatus {
654
655
  pending,
655
656
  complete: false,
656
657
  covered: coveredApprovalScopes(pending, now),
658
+ actions: pending.map(approvalAction),
657
659
  generatedAt,
658
660
  };
659
661
  }