granttap-mcp 0.6.5 → 0.6.7

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 (65) hide show
  1. package/README.md +94 -9
  2. package/apps/bridge/src/adapters.ts +50 -0
  3. package/apps/bridge/src/approval-state.ts +715 -0
  4. package/apps/bridge/src/approval.ts +219 -15
  5. package/apps/bridge/src/bin/claude-hook.ts +54 -14
  6. package/apps/bridge/src/bin/codex-hook.ts +42 -5
  7. package/apps/bridge/src/bin/codex-policy-hook.ts +38 -0
  8. package/apps/bridge/src/bin/connect.ts +8 -3
  9. package/apps/bridge/src/bin/cursor-after-shell.ts +97 -0
  10. package/apps/bridge/src/bin/cursor-hook.ts +96 -0
  11. package/apps/bridge/src/bin/cursor-mcp-hook.ts +103 -0
  12. package/apps/bridge/src/bin/pair-uri.ts +28 -0
  13. package/apps/bridge/src/bin/setup.ts +16 -2
  14. package/apps/bridge/src/bin/web.ts +33 -0
  15. package/apps/bridge/src/capabilities.ts +19 -5
  16. package/apps/bridge/src/cloud-approvals.ts +324 -0
  17. package/apps/bridge/src/config.ts +304 -10
  18. package/apps/bridge/src/cursor-mcp-policy.ts +163 -0
  19. package/apps/bridge/src/install.ts +491 -66
  20. package/apps/bridge/src/monitor.ts +192 -48
  21. package/apps/bridge/src/pair-uri-file.ts +46 -0
  22. package/apps/bridge/src/pairing.ts +4 -0
  23. package/apps/bridge/src/policy.ts +160 -0
  24. package/apps/bridge/src/sessions/activity-helpers.ts +149 -0
  25. package/apps/bridge/src/sessions/child-threads.ts +77 -0
  26. package/apps/bridge/src/sessions/claude.ts +549 -0
  27. package/apps/bridge/src/sessions/codex.ts +762 -0
  28. package/apps/bridge/src/sessions/common.ts +123 -0
  29. package/apps/bridge/src/sessions/copilot.ts +411 -0
  30. package/apps/bridge/src/sessions/cursor.ts +868 -0
  31. package/apps/bridge/src/sessions/telemetry.ts +562 -0
  32. package/apps/bridge/src/sessions.ts +184 -613
  33. package/apps/mcp/src/bin/authorize.ts +90 -0
  34. package/apps/mcp/src/bin/serve.ts +31 -0
  35. package/apps/mcp/src/bin/status.ts +39 -0
  36. package/apps/mcp/src/create-server.ts +344 -0
  37. package/apps/mcp/src/cursor-config.ts +148 -0
  38. package/apps/mcp/src/http-server.ts +234 -0
  39. package/apps/mcp/src/http-service.ts +396 -0
  40. package/apps/mcp/src/oauth-provider.ts +467 -0
  41. package/apps/mcp/src/pairing-status.ts +25 -0
  42. package/apps/mcp/src/provider-status.ts +239 -0
  43. package/apps/mcp/src/server.ts +6 -236
  44. package/bin/granttap-mcp.mjs +83 -17
  45. package/cursor-plugin/.cursor-plugin/marketplace.json +16 -0
  46. package/cursor-plugin/.cursor-plugin/plugin.json +29 -0
  47. package/cursor-plugin/README.md +73 -0
  48. package/cursor-plugin/assets/logo.svg +7 -0
  49. package/cursor-plugin/commands/connect.md +19 -0
  50. package/cursor-plugin/mcp.json +8 -0
  51. package/cursor-plugin/rules/dual-channel.mdc +11 -0
  52. package/cursor-plugin/skills/connect/SKILL.md +89 -0
  53. package/docs/cursor-authorize.md +67 -0
  54. package/docs/images/apple-watch-approval.png +0 -0
  55. package/docs/images/apple-watch-inbox.png +0 -0
  56. package/docs/images/apple-watch-task.png +0 -0
  57. package/docs/images/iphone-claude-tasks.png +0 -0
  58. package/docs/images/iphone-command-center.png +0 -0
  59. package/docs/images/iphone-mcp-usage.png +0 -0
  60. package/docs/images/iphone-task-detail.png +0 -0
  61. package/docs/images/phone-activity.png +0 -0
  62. package/docs/images/watch-approval.png +0 -0
  63. package/package.json +9 -1
  64. package/packages/core/relay-client.ts +11 -1
  65. package/packages/protocol/schema.ts +168 -11
package/README.md CHANGED
@@ -75,16 +75,23 @@ not separate MCP billing.
75
75
  Add GrantTap to each agent you use:
76
76
 
77
77
  ```bash
78
- codex mcp add granttap -- npx -y granttap-mcp@latest
79
- claude mcp add granttap -- npx -y granttap-mcp@latest
78
+ npm install
79
+ npm install -g .
80
+ codex mcp add granttap -- granttap
81
+ claude mcp add granttap -- granttap
80
82
  ```
81
83
 
84
+ Those commands use this checkout. Publish/release it before replacing the local
85
+ install with an npm registry version.
86
+
82
87
  Start a fresh agent task and say **“Connect GrantTap.”** The `connect` tool:
83
88
 
84
89
  1. creates a new end-to-end encrypted pairing;
85
- 2. returns a scannable one-time QR directly in the agent chat;
86
- 3. installs the Codex and Claude Code approval hooks; and
87
- 4. installs the per-user background helper for task sync and schedules.
90
+ 2. returns a scannable one-time QR directly in the agent chat.
91
+
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.
88
95
 
89
96
  Scan the QR with GrantTap on iPhone. No terminal QR, copied pairing JSON, or
90
97
  open background terminal is required.
@@ -93,8 +100,9 @@ If an MCP client cannot render image content, use the CLI fallback:
93
100
 
94
101
  ```bash
95
102
  npm install -g granttap-mcp
96
- granttap-mcp connect
97
- granttap-mcp setup
103
+ granttap connect
104
+ granttap setup
105
+ granttap status
98
106
  ```
99
107
 
100
108
  CLI `connect` prints a one-time QR and short manual code. `setup` is idempotent:
@@ -102,6 +110,72 @@ it preserves unrelated agent settings and backs up a configuration file before
102
110
  changing it. The pairing is stored locally in `~/.granttap/machine.json`.
103
111
  Existing beta state under `~/.nodvox/` is migrated automatically.
104
112
 
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.
115
+
116
+ ## Cursor Settings → Authorize
117
+
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**.
122
+
123
+ To enable Authorize for GrantTap, use the one-step local setup:
124
+
125
+ ```bash
126
+ npm install -g granttap-mcp
127
+ granttap authorize
128
+ ```
129
+
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.
139
+
140
+ The foreground troubleshooting flow is:
141
+
142
+ 1. Run the local HTTP OAuth server (loopback only):
143
+
144
+ ```bash
145
+ granttap serve
146
+ # listens on http://127.0.0.1:17342/mcp
147
+ ```
148
+
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
+ ```
161
+
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
+
105
179
  ## Codex and Claude Code: honest capability matrix
106
180
 
107
181
  | Capability | Codex | Claude Code |
@@ -133,7 +207,7 @@ Repository skills are discovered only in the selected task workspace under
133
207
  | `ask` | Sends an open question and waits for a spoken or typed reply |
134
208
  | `ask_yes_no` | Sends a yes/no question and waits for a tap |
135
209
  | `notify` | Sends a non-blocking status update |
136
- | `setup` | Registers both approval hooks and the terminal-free helper |
210
+ | `setup` | Registers Cursor/Claude/Codex policy hooks and the terminal-free helper |
137
211
 
138
212
  The default answer timeout is three minutes. Override it with
139
213
  `GRANTTAP_ASK_TIMEOUT_MS`.
@@ -189,8 +263,19 @@ time, agent, status, result, and created task ID.
189
263
  | Command | Purpose |
190
264
  | --- | --- |
191
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 |
192
268
  | `connect [relayUrl]` | Creates an E2EE pairing; optionally targets a self-hosted `wss://` relay |
193
- | `setup` | Registers the Codex/Claude hooks and background helper |
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.
194
279
 
195
280
  ## Development
196
281
 
@@ -113,6 +113,56 @@ export function decisionToCodexOutput(d: ApprovalDecision): unknown {
113
113
  };
114
114
  }
115
115
 
116
+ // --------------------------------------------------------------------- Cursor
117
+
118
+ export type CursorHookInput = {
119
+ conversation_id?: string;
120
+ generation_id?: string;
121
+ command?: string;
122
+ cwd?: string;
123
+ hook_event_name?: string;
124
+ workspace_roots?: string[];
125
+ session_id?: string;
126
+ tool_name?: string;
127
+ tool_input?: Record<string, unknown>;
128
+ };
129
+
130
+ export function cursorToRequest(input: CursorHookInput): ApprovalRequest {
131
+ const command = typeof input.command === "string" && input.command.trim()
132
+ ? input.command
133
+ : extractCommand("Shell", input.tool_input ?? {});
134
+ const sessionId = input.conversation_id ?? input.session_id;
135
+ const cwd = (typeof input.cwd === "string" && input.cwd.trim() ? input.cwd : undefined)
136
+ ?? (Array.isArray(input.workspace_roots) ? input.workspace_roots[0] : undefined);
137
+ return {
138
+ type: "approval.request",
139
+ requestId: randomId(6),
140
+ agent: "cursor",
141
+ kind: "permission",
142
+ tool: input.tool_name ?? "Shell",
143
+ title: shortTitle("Shell", command),
144
+ command,
145
+ cwd,
146
+ sessionId,
147
+ risk: guessRisk("Shell", command),
148
+ createdAt: Date.now(),
149
+ };
150
+ }
151
+
152
+ export function decisionToCursorOutput(d: ApprovalDecision): unknown {
153
+ const permission = d.decision === "allow" ? "allow" : "deny";
154
+ const message = d.note
155
+ ?? (d.decision === "allow" ? "Approved from GrantTap" : "Denied from GrantTap");
156
+ return {
157
+ permission,
158
+ continue: d.decision === "allow",
159
+ user_message: message,
160
+ agent_message: message,
161
+ userMessage: message,
162
+ agentMessage: message,
163
+ };
164
+ }
165
+
116
166
  // ------------------------------------------------------------------ helpers
117
167
 
118
168
  function shortTitle(tool: string, command: string): string {