granttap-mcp 0.7.6 → 0.8.2

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 (115) hide show
  1. package/LICENSE +86 -21
  2. package/README.md +130 -4
  3. package/THIRD_PARTY_NOTICES.md +23 -0
  4. package/apps/bridge/src/bin/claude-hook.ts +68 -55
  5. package/apps/bridge/src/bin/claude-prompt-hook.ts +40 -0
  6. package/apps/bridge/src/bin/codex-hook.ts +17 -5
  7. package/apps/bridge/src/bin/codex-policy-hook.ts +36 -10
  8. package/apps/bridge/src/bin/cursor-hook.ts +60 -7
  9. package/apps/bridge/src/bin/cursor-mcp-hook.ts +104 -36
  10. package/apps/bridge/src/bin/monitor.ts +15 -1
  11. package/apps/bridge/src/bin/setup.ts +15 -1
  12. package/apps/bridge/src/capabilities/README.md +2 -0
  13. package/apps/bridge/src/claude-bin.ts +112 -0
  14. package/apps/bridge/src/config/README.md +3 -0
  15. package/apps/bridge/src/config/runtime.ts +26 -0
  16. package/apps/bridge/src/cursor-mcp-policy.ts +36 -1
  17. package/apps/bridge/src/engine/README.md +25 -0
  18. package/apps/bridge/src/engine/engine-client.ts +204 -0
  19. package/apps/bridge/src/engine/engine-declaration.ts +74 -0
  20. package/apps/bridge/src/engine/engine-health.ts +22 -0
  21. package/apps/bridge/src/engine/engine-policy-protocol.ts +262 -0
  22. package/apps/bridge/src/engine/engine-policy-types.ts +37 -0
  23. package/apps/bridge/src/engine/engine-projects.ts +68 -0
  24. package/apps/bridge/src/engine/engine-protocol.ts +265 -0
  25. package/apps/bridge/src/engine/engine-supervisor.ts +175 -0
  26. package/apps/bridge/src/install.ts +78 -11
  27. package/apps/bridge/src/machine-load/README.md +10 -0
  28. package/apps/bridge/src/machine-load/agent-load-history.ts +94 -0
  29. package/apps/bridge/src/machine-load/index.ts +130 -0
  30. package/apps/bridge/src/machine-load/loop.ts +149 -0
  31. package/apps/bridge/src/machine-load/mcp-load-cache.ts +46 -0
  32. package/apps/bridge/src/machine-load/mcp-load-refresh.ts +65 -0
  33. package/apps/bridge/src/machine-load/mcp-process-sampler.ts +69 -0
  34. package/apps/bridge/src/machine-load/process-sampler.ts +99 -0
  35. package/apps/bridge/src/machine-load/scan-cost.ts +25 -0
  36. package/apps/bridge/src/mesh/README.md +10 -0
  37. package/apps/bridge/src/mesh/binding-state.ts +87 -0
  38. package/apps/bridge/src/mesh/capsule.ts +25 -1
  39. package/apps/bridge/src/mesh/catalog.ts +91 -11
  40. package/apps/bridge/src/mesh/checkpoint.ts +52 -0
  41. package/apps/bridge/src/mesh/computer-identity.ts +96 -0
  42. package/apps/bridge/src/mesh/convergence.ts +7 -5
  43. package/apps/bridge/src/mesh/execution-sweep.ts +35 -0
  44. package/apps/bridge/src/mesh/identity.ts +11 -0
  45. package/apps/bridge/src/mesh/integration-map.ts +122 -0
  46. package/apps/bridge/src/mesh/journal.ts +121 -0
  47. package/apps/bridge/src/mesh/map.ts +141 -0
  48. package/apps/bridge/src/mesh/observed-claims.ts +50 -0
  49. package/apps/bridge/src/mesh/observed-writes.ts +73 -0
  50. package/apps/bridge/src/mesh/other-side.ts +137 -0
  51. package/apps/bridge/src/mesh/prompt-context.ts +74 -0
  52. package/apps/bridge/src/mesh/readiness.ts +9 -1
  53. package/apps/bridge/src/mesh/run-digest.ts +118 -0
  54. package/apps/bridge/src/mesh/runtime.ts +13 -4
  55. package/apps/bridge/src/mesh/scoped-view.ts +32 -1
  56. package/apps/bridge/src/mesh/snapshot-merge.ts +32 -0
  57. package/apps/bridge/src/mesh/store-state.ts +120 -5
  58. package/apps/bridge/src/mesh/store-support.ts +39 -0
  59. package/apps/bridge/src/mesh/store.ts +187 -35
  60. package/apps/bridge/src/monitor-leadership.ts +139 -0
  61. package/apps/bridge/src/monitor.ts +82 -80
  62. package/apps/bridge/src/policy/README.md +23 -0
  63. package/apps/bridge/src/policy/capability-fingerprint.ts +211 -0
  64. package/apps/bridge/src/policy/codex-project-ask.ts +207 -0
  65. package/apps/bridge/src/policy/decision-log.ts +57 -0
  66. package/apps/bridge/src/policy/effective-action.ts +143 -0
  67. package/apps/bridge/src/project-policy/mapping.ts +156 -0
  68. package/apps/bridge/src/project-policy/runtime.ts +213 -0
  69. package/apps/bridge/src/reply/README.md +3 -0
  70. package/apps/bridge/src/reply/cursor-agent-bin.ts +46 -0
  71. package/apps/bridge/src/reply/process.ts +3 -0
  72. package/apps/bridge/src/reply/provider-headless.ts +2 -1
  73. package/apps/bridge/src/reply/types.ts +3 -0
  74. package/apps/bridge/src/reply.ts +36 -10
  75. package/apps/bridge/src/session-keys.ts +16 -1
  76. package/apps/bridge/src/sessions/activity-helpers.ts +64 -5
  77. package/apps/bridge/src/sessions/capability-totals.ts +0 -0
  78. package/apps/bridge/src/sessions/claude.ts +5 -1
  79. package/apps/bridge/src/sessions/codex-head.ts +77 -0
  80. package/apps/bridge/src/sessions/codex.ts +26 -24
  81. package/apps/bridge/src/sessions/cursor/README.md +3 -0
  82. package/apps/bridge/src/sessions/cursor/transcripts.ts +5 -1
  83. package/apps/bridge/src/sessions/telemetry/README.md +3 -0
  84. package/apps/bridge/src/sessions/telemetry/identity.ts +1 -0
  85. package/apps/bridge/src/sessions/telemetry.ts +17 -0
  86. package/apps/bridge/src/sessions.ts +33 -6
  87. package/apps/bridge/src/tools/update-handler.ts +23 -0
  88. package/apps/bridge/src/tools/updater.ts +265 -0
  89. package/apps/bridge/src/tools/version.ts +113 -0
  90. package/apps/mcp/src/http-server.ts +3 -2
  91. package/apps/mcp/src/http-service/README.md +2 -0
  92. package/apps/mcp/src/http-service/common.ts +16 -1
  93. package/apps/mcp/src/mcp-tools/README.md +3 -0
  94. package/apps/mcp/src/mcp-tools/mesh-resource.ts +23 -0
  95. package/apps/mcp/src/oauth/README.md +2 -0
  96. package/apps/mcp/src/oauth/loopback-origin.ts +16 -0
  97. package/bin/granttap-mcp.mjs +2 -2
  98. package/cursor-plugin/.cursor-plugin/plugin.json +1 -1
  99. package/cursor-plugin/LICENSE +86 -0
  100. package/cursor-plugin/README.md +5 -0
  101. package/docs/images/iphone-chat.png +0 -0
  102. package/docs/images/iphone-claude-tasks.png +0 -0
  103. package/docs/images/iphone-command-center.png +0 -0
  104. package/docs/images/iphone-mcp-usage.png +0 -0
  105. package/package.json +4 -3
  106. package/packages/core/relay-client-types.ts +9 -0
  107. package/packages/core/relay-client.ts +50 -0
  108. package/packages/protocol/messages/README.md +12 -0
  109. package/packages/protocol/messages/capabilities.ts +54 -0
  110. package/packages/protocol/messages/interaction.ts +9 -0
  111. package/packages/protocol/messages/machine.ts +29 -0
  112. package/packages/protocol/messages/mesh.ts +53 -1
  113. package/packages/protocol/messages/project-policy.ts +172 -0
  114. package/packages/protocol/messages/sessions.ts +32 -0
  115. package/packages/protocol/schema.ts +16 -0
package/LICENSE CHANGED
@@ -1,21 +1,86 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Serhii Ziborov
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ GrantTap Commercial Source License 1.0
2
+
3
+ Copyright (c) 2026 Serhii Ziborov. All rights reserved.
4
+
5
+ This license governs the source code and software distributed from the
6
+ granttap-mcp repository and package (the "Software"), except for third-party
7
+ components that identify their own license terms.
8
+
9
+ 1. Definitions
10
+
11
+ "GrantTap" means Serhii Ziborov doing business as GrantTap.
12
+
13
+ "Authorized Access" means an active paid GrantTap subscription, an official
14
+ GrantTap trial, or a separate written commercial agreement with GrantTap.
15
+
16
+ "Authorized User" means a person or legal entity covered by Authorized Access.
17
+
18
+ 2. Source inspection and contribution
19
+
20
+ You may download, inspect, and make local modifications to the source solely to
21
+ evaluate the Software, perform security review, or prepare a contribution to
22
+ the official GrantTap repository. You may create a public fork solely for that
23
+ contribution workflow if the fork retains this license and does not publish a
24
+ release, binary, hosted service, or independently usable distribution.
25
+
26
+ These rights do not permit production use, commercial use, or use to provide a
27
+ service to another person.
28
+
29
+ 3. Authorized product use
30
+
31
+ During Authorized Access, GrantTap grants the Authorized User a limited,
32
+ non-exclusive, non-transferable, non-sublicensable license to install, execute,
33
+ and internally modify the Software on devices the Authorized User owns or
34
+ controls, solely to use the official GrantTap product and services.
35
+
36
+ When Authorized Access ends, the Authorized User must stop using the Software
37
+ except where a separate written agreement says otherwise.
38
+
39
+ 4. Restrictions
40
+
41
+ Unless GrantTap gives prior written permission, you may not:
42
+
43
+ - sell, sublicense, redistribute, or publish the Software or a derivative work;
44
+ - offer the Software or a derivative work as a hosted or managed service;
45
+ - use the Software to build or operate a competing product or service;
46
+ - use the Software as a standalone general-purpose coding-agent runtime outside
47
+ the official GrantTap product;
48
+ - bypass subscription, license, access-control, or entitlement checks;
49
+ - remove or alter copyright, license, attribution, or proprietary notices; or
50
+ - use GrantTap names or marks to imply endorsement of a modified distribution.
51
+
52
+ 5. Ownership
53
+
54
+ The Software is licensed, not sold. GrantTap retains all right, title, and
55
+ interest in the Software and its derivative works. No trademark, patent, or
56
+ other rights are granted except the limited copyright rights stated here.
57
+
58
+ 6. Third-party components
59
+
60
+ Third-party components remain governed by their respective licenses. Those
61
+ licenses and notices are not replaced by this license. See
62
+ THIRD_PARTY_NOTICES.md where distributed.
63
+
64
+ 7. Termination
65
+
66
+ This license terminates automatically if you breach its terms. On termination,
67
+ you must stop using and distributing the Software and delete copies that you
68
+ are not legally required to retain. Sections 4 through 9 survive termination.
69
+
70
+ 8. Disclaimer of warranty
71
+
72
+ THE SOFTWARE IS PROVIDED "AS IS" AND "AS AVAILABLE", WITHOUT WARRANTIES OF
73
+ ANY KIND, EXPRESS OR IMPLIED, INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS
74
+ FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT.
75
+
76
+ 9. Limitation of liability
77
+
78
+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, GRANTTAP AND ITS CONTRIBUTORS WILL NOT
79
+ BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR
80
+ PUNITIVE DAMAGES, OR FOR LOSS OF DATA, PROFITS, REVENUE, OR BUSINESS, ARISING
81
+ FROM OR RELATED TO THE SOFTWARE OR THIS LICENSE.
82
+
83
+ 10. Commercial terms
84
+
85
+ Authorized Access, pricing, and separate commercial licenses are available
86
+ through https://granttap.com.
package/README.md CHANGED
@@ -2,7 +2,7 @@
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
- [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
5
+ [![License](https://img.shields.io/badge/license-GrantTap%20Commercial-7c3aed.svg)](LICENSE)
6
6
 
7
7
  GrantTap is a Personal live control center for local coding agents.
8
8
 
@@ -13,6 +13,10 @@ hooks, local adapters, and TypeScript wire schemas. Agents and provider
13
13
  credentials stay on your computer. Native iPhone and Apple Watch traffic is
14
14
  end-to-end encrypted.
15
15
 
16
+ The source is public for inspection and contribution, but GrantTap MCP is
17
+ proprietary paid software rather than open source. Production use requires an
18
+ active GrantTap subscription or a separate commercial agreement.
19
+
16
20
  [Website](https://granttap.com) · [npm](https://www.npmjs.com/package/granttap-mcp) ·
17
21
  [Security model](SECURITY.md) ·
18
22
  [Relay source](https://github.com/sergii-ziborov/granttap-relay)
@@ -74,11 +78,34 @@ parity.
74
78
  A capsule carries facts, not files. A handoff from a checkout with uncommitted
75
79
  changes is refused — "This task has uncommitted changes. Commit or checkpoint
76
80
  them before moving the task." — instead of silently continuing the Task from
77
- committed state and leaving that work behind. The destination is refused just
81
+ committed state and leaving that work behind. Asked to checkpoint from the
82
+ phone, the source computer commits everything to `granttap/checkpoint/<task>`
83
+ from a temporary index, so HEAD, the current branch, and the working tree stay
84
+ exactly as the agent left them, and the capsule carries that commit. Nothing is
85
+ pushed; the destination says so if the commit has not reached it.
86
+
87
+ Claims do not wait for an agent to announce them. Every edit an agent makes is
88
+ visible in its transcript, so the runtime derives an intent claim from each
89
+ recent write — marked as seen rather than said, and expiring ten minutes after
90
+ the writing stops. Overlap is judged twice: the same file is a conflict, and
91
+ the same module is the warning that comes before it. A module is recognised
92
+ from the path alone, so every computer and the phone reach the same answer,
93
+ and the Task screen names who else is in this Task's files or modules while it
94
+ can still be avoided. The destination is refused just
78
95
  as explicitly when the named commit is not on its computer, or when the
79
96
  capsule's own resource claims overlap another execution's; GrantTap never
80
97
  pushes or fetches on its own.
81
98
 
99
+ A Project usually binds more than one repository, and a bound repository can
100
+ say which of the others sit on the far side of its databases, topics, and APIs:
101
+ commit a [`WEAVATRIX.md`](https://github.com/Weavatrix/weavatrix-md) next to the
102
+ README and the runtime reads it — only the edges it states, nothing inferred —
103
+ and publishes them with the Project. The Task screen then names another Task
104
+ that is working on the other side of a contract this Task touches (the consumer
105
+ of a topic it produces, the caller of an API it changes), and the scoped
106
+ `granttap://mesh/{capability}` resource gives the agent the same `peers`,
107
+ `otherSide`, and `neighbours` so it can coordinate before it commits.
108
+
82
109
  ### Grok Bot as a scoped Mesh participant
83
110
 
84
111
  Grok Bot is a persistent agent, not a coding-agent integration. The iPhone
@@ -97,6 +124,9 @@ history stays on the device.
97
124
 
98
125
  ## Install
99
126
 
127
+ Installation and production use require Authorized Access under the
128
+ [GrantTap Commercial Source License](LICENSE).
129
+
100
130
  ```bash
101
131
  npm install -g granttap-mcp
102
132
  granttap setup
@@ -107,10 +137,17 @@ hooks, installs the background helper, configures Cursor's persistent local
107
137
  OAuth service when Cursor is present, and starts phone pairing when run in an
108
138
  interactive terminal. It ends with one exact next action.
109
139
 
140
+ Setup also declares the separately distributed GrantTap Engine, which Project
141
+ Governance needs before it can report anything. The standard locations are
142
+ searched, and `--engine <path>` points at one that lives elsewhere; the binary
143
+ is checksummed here, because that checksum is the only thing verified before it
144
+ is launched. Without an engine the rollout stays off and setup says so, rather
145
+ than leaving "Governance not reported" on the phone as the only symptom.
146
+
110
147
  The normal CLI surface is intentionally small:
111
148
 
112
149
  ```text
113
- granttap setup
150
+ granttap setup [--engine <path>]
114
151
  granttap status [--json]
115
152
  granttap connect [--relay <wss-url>]
116
153
  granttap reset [--yes]
@@ -162,11 +199,64 @@ The bounded encrypted protocol preserves:
162
199
  - visible activity, delivery state, context and token counters;
163
200
  - MCP, Skill, and CLI observations;
164
201
  - child-agent relationships;
165
- - per-capability outcome: `success`, `error`, `cancelled`, or `unknown`.
202
+ - per-capability outcome: `success`, `error`, `cancelled`, or `unknown`;
203
+ - what a call cost, where the machine can be observed while it ran.
166
204
 
167
205
  An optional bounded `errorClass` may describe an error category. Full tool
168
206
  error payloads are not copied into usage telemetry by default.
169
207
 
208
+ ### One computer, whatever the network calls it
209
+
210
+ The Mesh keys a computer by an identity written down once, on first use, in
211
+ `computer.json` in the config directory — the hostname it had then — and
212
+ keeps it. A Mac renamed by the network it joins ("Mac.lan" at home,
213
+ "Serhiis-MacBook-Pro.local" elsewhere) used to become a second computer with
214
+ its own open executions and repository bindings; now every later hostname is
215
+ remembered as a former name of the same machine, its leftover executions are
216
+ closed and its bindings marked unavailable, and the current hostname stays
217
+ what people see. `GRANTTAP_COMPUTER_ID` overrides the stored id.
218
+
219
+ ### Run journal and prompt-time context
220
+
221
+ A message from the phone is answered by a fresh `claude -p --resume` of the
222
+ same chat. Its turns land in the transcript, but a session holding that chat
223
+ open never sees them — its context was built before they happened. The runtime
224
+ therefore journals every delivery: what was asked (without the attachment
225
+ note), what came back, which files were written, how many tool calls it took,
226
+ and whether the run was cut off by the ten-minute delivery limit. The Task
227
+ carries the same digest as `TASK_PROGRESS`, so the phone's timeline and the
228
+ Mesh show it. A `UserPromptSubmit` hook, installed beside the approval hook by
229
+ `granttap setup`, adds the unread journal to the next prompt of the live
230
+ session together with the Mesh brief — the other live Tasks in the Project,
231
+ who is in the same file or module, the other side of the repository, and any
232
+ question still unanswered — and names the MCP resource
233
+ `granttap://mesh/{capability}/map`, one page of markdown with the whole
234
+ Project: Tasks, who edits which module, the other side of each repository,
235
+ dependencies, and what just happened. Background runs themselves receive
236
+ nothing from the hook; the journal is kept for the session a person is in.
237
+
238
+ ### Tool versions and updates from the phone
239
+
240
+ The status also names each provider's command-line tool as it answers on this
241
+ computer — its version, how it is kept current, and, for Claude Code, whether a
242
+ newer copy already sits on the disk (the Claude desktop app keeps its own; the
243
+ runtime uses the newest one it finds). A tool that lags the rest of the
244
+ environment fails in ways the phone can only report, so the phone can ask this
245
+ computer to update one: the phone names only the tool, and the command is the
246
+ runtime's, fixed by how the tool was installed — `claude update`, `agent
247
+ update`, `grok update`, the npm that owns the tool's prefix, or Homebrew. The
248
+ runtime never downloads a tool itself; a tool installed by an installer script
249
+ is left to a trusted terminal, with the command spelled out in the answer. The
250
+ result — version before and after, the updater's own output — comes back as
251
+ `tool.update.result`.
252
+
253
+ Cost is reported as attributed rather than measured, because that is what it
254
+ is. A call is read back from the transcript once it has finished, so it can
255
+ never be measured directly: an MCP server outlives its calls and is sampled
256
+ directly, while a built-in tool leaves nothing behind and is costed from the
257
+ samples that fall inside its own start and end. A call with no sample near it
258
+ reports nothing rather than a number borrowed from another moment.
259
+
170
260
  ## Local enforcement
171
261
 
172
262
  GrantTap can narrow later actions for an exact task only where a provider
@@ -184,6 +274,34 @@ The user-facing approval modes map to the existing runtime policy:
184
274
 
185
275
  Legacy custom levels remain compatible but are not part of the primary flow.
186
276
 
277
+ ## Project Governance
278
+
279
+ Capabilities are decided per Project, not per task. A policy names an effect —
280
+ `allow`, `ask`, or `deny` — for each kind (skills, MCP servers, shell and
281
+ scripts, file writes, deploy, network), and may name one capability alone: one
282
+ MCP server can be forbidden without forbidding every server. A named rule wins
283
+ over its kind, and a global deny always wins over a Project.
284
+
285
+ The phone authors the policy and hands it to every Project computer through the
286
+ relay, which holds the encrypted packet until each computer reads its mailbox;
287
+ a computer that was asleep receives it when it returns. Each computer applies
288
+ the policy through the GrantTap Engine, acknowledges the revision it enforces,
289
+ and reports coverage — enforced, observed only, unsupported, or unknown — per
290
+ capability kind, so the phone shows what is actually in force rather than what
291
+ was sent. Revision zero is a Project with no policy yet, and it is reported so
292
+ the first policy can be written.
293
+
294
+ Evaluation happens in the provider hook before the action runs, with a deadline
295
+ long enough for a busy machine to answer. A missed answer falls back to the
296
+ legacy GrantTap gate rather than to a silent allow, and content never crosses
297
+ to the engine: it receives a capability fingerprint, not the command or file.
298
+
299
+ A refusal is said where the action was. Each one is written down for its chat,
300
+ and the timeline carries it as a status row naming the rule and the reason,
301
+ beside the call it stopped. On the phone a rule is written where the need for
302
+ it appears: touch and hold a tool on the Project page, or use the Governance
303
+ menu on a tool's call history, to allow, ask, or deny it for the Project.
304
+
187
305
  ## Relay boundary
188
306
 
189
307
  Pairing and task keys are generated locally. The relay receives opaque routing
@@ -217,4 +335,12 @@ gate on macOS, and `npm publish` enforces the same gate through
217
335
  Do not publish from a dirty checkout or before the package allowlist, tests,
218
336
  typecheck, and release checks pass.
219
337
 
338
+ ## License
339
+
340
+ GrantTap MCP is distributed under the proprietary
341
+ [GrantTap Commercial Source License 1.0](LICENSE). Public source access does not
342
+ grant open-source, redistribution, hosted-service, or competing-product rights.
343
+ Third-party dependencies retain their own terms; see
344
+ [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
345
+
220
346
  GrantTap is not affiliated with Anthropic, OpenAI, Apple, Anysphere, or xAI.
@@ -0,0 +1,23 @@
1
+ # Third-party notices
2
+
3
+ GrantTap MCP is proprietary software distributed under the GrantTap Commercial
4
+ Source License. Third-party components retain their own licenses; the GrantTap
5
+ license does not replace or restrict those terms.
6
+
7
+ The npm package declares, rather than vendors, these direct runtime
8
+ dependencies. Their packages carry the applicable license texts:
9
+
10
+ | Component | License |
11
+ | --- | --- |
12
+ | `@modelcontextprotocol/sdk` | MIT |
13
+ | `express` | MIT |
14
+ | `qrcode` | MIT |
15
+ | `tsx` | MIT |
16
+ | `tweetnacl` | The Unlicense |
17
+ | `ws` | MIT |
18
+ | `zod` | MIT |
19
+
20
+ The unpublished Rust workspace also depends on separately licensed components,
21
+ including `mcport` (MIT), `blazingly-json` (MIT), `serde` (MIT OR Apache-2.0), and
22
+ BlindPlane crates (MIT OR Apache-2.0). Binary distributions must retain the
23
+ complete license texts required by the exact dependency versions they contain.
@@ -12,6 +12,7 @@
12
12
  * Fails closed: any error or timeout denies the tool call rather than letting it
13
13
  * through unattended — unless GrantTap auto-accept allows it locally.
14
14
  */
15
+ import { recordProjectDecision } from "../policy/decision-log";
15
16
  import { claudeToRequest, decisionToClaudeOutput, type HookInput } from "../adapters";
16
17
  import { isUnanswered, requestApproval } from "../approval";
17
18
  import {
@@ -25,6 +26,10 @@ import {
25
26
  } from "../config";
26
27
  import { recordAttributedCall } from "../mesh/call-scope";
27
28
  import { classifyAction } from "../policy";
29
+ import {
30
+ evaluateEffectiveAction,
31
+ legacyGrantTapFlowAllowed,
32
+ } from "../policy/effective-action";
28
33
  import { protectedGrantTapAccess } from "../self-protection";
29
34
 
30
35
  async function readStdin(): Promise<string> {
@@ -35,31 +40,20 @@ async function readStdin(): Promise<string> {
35
40
 
36
41
  async function main(): Promise<void> {
37
42
  const raw = await readStdin();
38
- let input: HookInput = {};
43
+ let input: HookInput;
39
44
  try {
40
45
  input = JSON.parse(raw) as HookInput;
41
46
  } catch {
42
- process.stdout.write(
43
- JSON.stringify({
44
- hookSpecificOutput: {
45
- hookEventName: "PreToolUse",
46
- permissionDecision: "deny",
47
- permissionDecisionReason: "GrantTap received invalid hook JSON",
48
- },
49
- }),
50
- );
47
+ writePermission("deny", "GrantTap received invalid hook JSON");
51
48
  return;
52
49
  }
50
+ await handlePreToolUse(input);
51
+ }
53
52
 
53
+ async function handlePreToolUse(input: HookInput): Promise<void> {
54
54
  const protectedAccess = protectedGrantTapAccess(input.tool_name, input.tool_input);
55
55
  if (protectedAccess) {
56
- process.stdout.write(JSON.stringify({
57
- hookSpecificOutput: {
58
- hookEventName: "PreToolUse",
59
- permissionDecision: "deny",
60
- permissionDecisionReason: protectedAccess.reason,
61
- },
62
- }));
56
+ writePermission("deny", protectedAccess.reason);
63
57
  return;
64
58
  }
65
59
 
@@ -80,43 +74,49 @@ async function main(): Promise<void> {
80
74
  input.tool_name,
81
75
  input.tool_input,
82
76
  );
83
- if (blocked) {
84
- process.stdout.write(
85
- JSON.stringify({
86
- hookSpecificOutput: {
87
- hookEventName: "PreToolUse",
88
- permissionDecision: "deny",
89
- permissionDecisionReason: blocked.reason,
90
- },
91
- }),
92
- );
77
+ const projectDecision = await evaluateEffectiveAction({
78
+ provider: "claude",
79
+ sessionId: input.session_id,
80
+ cwd: input.cwd,
81
+ toolName: input.tool_name,
82
+ toolInput: input.tool_input,
83
+ legacyDenyReason: blocked?.reason,
84
+ });
85
+ if (projectDecision.effect === "deny") {
86
+ // Say so where the action was, not only to the agent.
87
+ if (input.session_id) {
88
+ recordProjectDecision(input.session_id, {
89
+ at: Date.now(), toolName: input.tool_name ?? "tool", reason: projectDecision.reason,
90
+ ruleId: projectDecision.rule_id,
91
+ });
92
+ }
93
+ writePermission("deny", projectDecision.reason);
93
94
  return;
94
95
  }
96
+ const legacyFlow = legacyGrantTapFlowAllowed(projectDecision);
95
97
 
96
98
  // Claude already granted this chat unconditional tool access. GrantTap's
97
- // explicit MCP/skill/CLI blocks above still win, but phone approval must not
98
- // re-prompt a call Claude is intentionally running in bypass mode.
99
- if (input.permission_mode === "bypassPermissions") return;
99
+ // Project ASK/DENY is a parent boundary, so bypass mode cannot weaken it.
100
+ if (legacyFlow && input.permission_mode === "bypassPermissions") return;
100
101
 
101
102
  // Gating paused, or this session is exempt → abstain (empty output = Claude
102
103
  // uses its normal permission flow, exactly as if GrantTap weren't installed).
103
- if (isGatingSkipped(input.session_id)) return;
104
+ if (legacyFlow && isGatingSkipped(input.session_id)) return;
105
+ await continuePermissionFlow(input, legacyFlow);
106
+ }
104
107
 
108
+ async function continuePermissionFlow(input: HookInput, legacyFlow: boolean): Promise<void> {
105
109
  let cfg;
106
110
  try {
107
111
  cfg = loadConfig(machineConfigPath());
108
112
  } catch {
113
+ if (!legacyFlow) {
114
+ writePermission("deny", "Project policy requires GrantTap approval, but this computer is not paired");
115
+ return;
116
+ }
109
117
  // No pairing yet: don't block the user's normal workflow — defer to Claude's
110
118
  // own prompt by emitting "ask".
111
- process.stdout.write(
112
- JSON.stringify({
113
- hookSpecificOutput: {
114
- hookEventName: "PreToolUse",
115
- permissionDecision: "ask",
116
- permissionDecisionReason: "GrantTap not paired (run `npm run init`)",
117
- },
118
- }),
119
- );
119
+ writePermission("ask", "GrantTap not paired (run `npm run init`)");
120
120
  return;
121
121
  }
122
122
 
@@ -125,21 +125,8 @@ async function main(): Promise<void> {
125
125
  // the user never finished setting up. Once paired, this is what keeps routine
126
126
  // work independent of whether the phone is awake, reachable, or even running.
127
127
  const req = claudeToRequest(input);
128
- if (shouldAutoAcceptTool(input.session_id, req.tool, req.command)) {
129
- const level = autoAcceptLevelFor(input.session_id);
130
- const cls = classifyAction(req.tool, req.command);
131
- process.stdout.write(
132
- JSON.stringify(
133
- decisionToClaudeOutput({
134
- type: "approval.decision",
135
- requestId: req.requestId,
136
- decision: "allow",
137
- decidedBy: "auto",
138
- note: `GrantTap auto-accept (${level} / ${cls})`,
139
- decidedAt: Date.now(),
140
- }),
141
- ),
142
- );
128
+ if (legacyFlow && shouldAutoAcceptTool(input.session_id, req.tool, req.command)) {
129
+ writeAutoApproval(input, req);
143
130
  return;
144
131
  }
145
132
 
@@ -153,12 +140,38 @@ async function main(): Promise<void> {
153
140
  // Relay down or phone asleep ≠ phone said no: abstain so Claude's local flow
154
141
  // handles it (avoid flooding "решай локально" on every tool).
155
142
  if (isUnanswered(decision)) {
143
+ if (!legacyFlow) {
144
+ writePermission("deny", "Project approval was required but no decision was received");
145
+ }
156
146
  return;
157
147
  }
158
148
 
159
149
  process.stdout.write(JSON.stringify(decisionToClaudeOutput(decision)));
160
150
  }
161
151
 
152
+ function writeAutoApproval(input: HookInput, req: ReturnType<typeof claudeToRequest>): void {
153
+ const level = autoAcceptLevelFor(input.session_id);
154
+ const cls = classifyAction(req.tool, req.command);
155
+ process.stdout.write(JSON.stringify(decisionToClaudeOutput({
156
+ type: "approval.decision",
157
+ requestId: req.requestId,
158
+ decision: "allow",
159
+ decidedBy: "auto",
160
+ note: `GrantTap auto-accept (${level} / ${cls})`,
161
+ decidedAt: Date.now(),
162
+ })));
163
+ }
164
+
165
+ function writePermission(decision: "deny" | "ask", reason: string): void {
166
+ process.stdout.write(JSON.stringify({
167
+ hookSpecificOutput: {
168
+ hookEventName: "PreToolUse",
169
+ permissionDecision: decision,
170
+ permissionDecisionReason: reason,
171
+ },
172
+ }));
173
+ }
174
+
162
175
  main().catch((err) => {
163
176
  process.stdout.write(
164
177
  JSON.stringify({
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env -S npx tsx
2
+ /**
3
+ * Claude Code UserPromptSubmit hook entry point.
4
+ *
5
+ * Registered in ~/.claude/settings.json so that when a person submits a
6
+ * prompt, this program adds what the chat could not know by itself: the
7
+ * background runs that answered phone messages in it, and the Mesh brief for
8
+ * its Task. It never blocks a prompt — anything wrong, and it adds nothing.
9
+ *
10
+ * Reads: UserPromptSubmit JSON on stdin ({ session_id, prompt, cwd, ... })
11
+ * Writes: { hookSpecificOutput: { hookEventName, additionalContext } } on stdout
12
+ */
13
+ import { promptContext } from "../mesh/prompt-context";
14
+
15
+ async function readStdin(): Promise<string> {
16
+ const chunks: Buffer[] = [];
17
+ for await (const chunk of process.stdin) chunks.push(chunk as Buffer);
18
+ return Buffer.concat(chunks).toString("utf8");
19
+ }
20
+
21
+ async function main(): Promise<void> {
22
+ // A background run answering the phone is itself a prompt submission; it is
23
+ // not the live session the journal is kept for.
24
+ if (process.env.GRANTTAP_DELIVERY) return;
25
+ const raw = await readStdin();
26
+ let input: { session_id?: unknown };
27
+ try {
28
+ input = JSON.parse(raw) as { session_id?: unknown };
29
+ } catch {
30
+ return;
31
+ }
32
+ const sessionId = typeof input.session_id === "string" ? input.session_id.trim() : "";
33
+ const text = promptContext(sessionId);
34
+ if (!text) return;
35
+ process.stdout.write(JSON.stringify({
36
+ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: text },
37
+ }));
38
+ }
39
+
40
+ main().catch(() => undefined).finally(() => process.exit(0));
@@ -25,6 +25,7 @@ import {
25
25
  } from "../config";
26
26
  import { recordAttributedCall } from "../mesh/call-scope";
27
27
  import { classifyAction } from "../policy";
28
+ import { consumeCodexProjectAsk } from "../policy/codex-project-ask";
28
29
  import { protectedGrantTapAccess } from "../self-protection";
29
30
 
30
31
  async function readStdin(): Promise<string> {
@@ -77,9 +78,15 @@ async function main(): Promise<void> {
77
78
  process.stdout.write(denyOutput(blocked.reason));
78
79
  return;
79
80
  }
81
+ const projectAsk = consumeCodexProjectAsk({
82
+ sessionId: input.session_id,
83
+ toolUseId: input.tool_use_id,
84
+ toolName: input.tool_name,
85
+ toolInput: input.tool_input,
86
+ });
80
87
 
81
88
  // Gating paused or this session exempt → stay silent (Codex uses its own flow).
82
- if (isGatingSkipped(input.session_id)) return;
89
+ if (!projectAsk && isGatingSkipped(input.session_id)) return;
83
90
 
84
91
  let cfg;
85
92
  try {
@@ -91,7 +98,7 @@ async function main(): Promise<void> {
91
98
 
92
99
  // Local policy, evaluated after pairing is confirmed — see claude-hook.
93
100
  const req = codexToRequest(input);
94
- if (shouldAutoAcceptTool(input.session_id, req.tool, req.command)) {
101
+ if (!projectAsk && shouldAutoAcceptTool(input.session_id, req.tool, req.command)) {
95
102
  const level = autoAcceptLevelFor(input.session_id);
96
103
  const cls = classifyAction(req.tool, req.command);
97
104
  process.stdout.write(
@@ -116,9 +123,14 @@ async function main(): Promise<void> {
116
123
  );
117
124
  const decision = await requestApproval(cfg, req, { timeoutMs });
118
125
 
119
- // Relay down or phone never answered: stay silent an empty hook result
120
- // sends Codex back to its own approval flow, so desk work keeps going.
121
- if (isUnanswered(decision)) return;
126
+ // Routine relay failure stays in Codex's native flow. A Project ASK cannot
127
+ // fall through to provider approval because that would weaken the parent rule.
128
+ if (isUnanswered(decision)) {
129
+ if (projectAsk) {
130
+ process.stdout.write(denyOutput("Project approval was required but no decision was received"));
131
+ }
132
+ return;
133
+ }
122
134
 
123
135
  process.stdout.write(JSON.stringify(decisionToCodexOutput(decision)));
124
136
  }