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.
- package/LICENSE +86 -21
- package/README.md +130 -4
- package/THIRD_PARTY_NOTICES.md +23 -0
- package/apps/bridge/src/bin/claude-hook.ts +68 -55
- package/apps/bridge/src/bin/claude-prompt-hook.ts +40 -0
- package/apps/bridge/src/bin/codex-hook.ts +17 -5
- package/apps/bridge/src/bin/codex-policy-hook.ts +36 -10
- package/apps/bridge/src/bin/cursor-hook.ts +60 -7
- package/apps/bridge/src/bin/cursor-mcp-hook.ts +104 -36
- package/apps/bridge/src/bin/monitor.ts +15 -1
- package/apps/bridge/src/bin/setup.ts +15 -1
- package/apps/bridge/src/capabilities/README.md +2 -0
- package/apps/bridge/src/claude-bin.ts +112 -0
- package/apps/bridge/src/config/README.md +3 -0
- package/apps/bridge/src/config/runtime.ts +26 -0
- package/apps/bridge/src/cursor-mcp-policy.ts +36 -1
- package/apps/bridge/src/engine/README.md +25 -0
- package/apps/bridge/src/engine/engine-client.ts +204 -0
- package/apps/bridge/src/engine/engine-declaration.ts +74 -0
- package/apps/bridge/src/engine/engine-health.ts +22 -0
- package/apps/bridge/src/engine/engine-policy-protocol.ts +262 -0
- package/apps/bridge/src/engine/engine-policy-types.ts +37 -0
- package/apps/bridge/src/engine/engine-projects.ts +68 -0
- package/apps/bridge/src/engine/engine-protocol.ts +265 -0
- package/apps/bridge/src/engine/engine-supervisor.ts +175 -0
- package/apps/bridge/src/install.ts +78 -11
- package/apps/bridge/src/machine-load/README.md +10 -0
- package/apps/bridge/src/machine-load/agent-load-history.ts +94 -0
- package/apps/bridge/src/machine-load/index.ts +130 -0
- package/apps/bridge/src/machine-load/loop.ts +149 -0
- package/apps/bridge/src/machine-load/mcp-load-cache.ts +46 -0
- package/apps/bridge/src/machine-load/mcp-load-refresh.ts +65 -0
- package/apps/bridge/src/machine-load/mcp-process-sampler.ts +69 -0
- package/apps/bridge/src/machine-load/process-sampler.ts +99 -0
- package/apps/bridge/src/machine-load/scan-cost.ts +25 -0
- package/apps/bridge/src/mesh/README.md +10 -0
- package/apps/bridge/src/mesh/binding-state.ts +87 -0
- package/apps/bridge/src/mesh/capsule.ts +25 -1
- package/apps/bridge/src/mesh/catalog.ts +91 -11
- package/apps/bridge/src/mesh/checkpoint.ts +52 -0
- package/apps/bridge/src/mesh/computer-identity.ts +96 -0
- package/apps/bridge/src/mesh/convergence.ts +7 -5
- package/apps/bridge/src/mesh/execution-sweep.ts +35 -0
- package/apps/bridge/src/mesh/identity.ts +11 -0
- package/apps/bridge/src/mesh/integration-map.ts +122 -0
- package/apps/bridge/src/mesh/journal.ts +121 -0
- package/apps/bridge/src/mesh/map.ts +141 -0
- package/apps/bridge/src/mesh/observed-claims.ts +50 -0
- package/apps/bridge/src/mesh/observed-writes.ts +73 -0
- package/apps/bridge/src/mesh/other-side.ts +137 -0
- package/apps/bridge/src/mesh/prompt-context.ts +74 -0
- package/apps/bridge/src/mesh/readiness.ts +9 -1
- package/apps/bridge/src/mesh/run-digest.ts +118 -0
- package/apps/bridge/src/mesh/runtime.ts +13 -4
- package/apps/bridge/src/mesh/scoped-view.ts +32 -1
- package/apps/bridge/src/mesh/snapshot-merge.ts +32 -0
- package/apps/bridge/src/mesh/store-state.ts +120 -5
- package/apps/bridge/src/mesh/store-support.ts +39 -0
- package/apps/bridge/src/mesh/store.ts +187 -35
- package/apps/bridge/src/monitor-leadership.ts +139 -0
- package/apps/bridge/src/monitor.ts +82 -80
- package/apps/bridge/src/policy/README.md +23 -0
- package/apps/bridge/src/policy/capability-fingerprint.ts +211 -0
- package/apps/bridge/src/policy/codex-project-ask.ts +207 -0
- package/apps/bridge/src/policy/decision-log.ts +57 -0
- package/apps/bridge/src/policy/effective-action.ts +143 -0
- package/apps/bridge/src/project-policy/mapping.ts +156 -0
- package/apps/bridge/src/project-policy/runtime.ts +213 -0
- package/apps/bridge/src/reply/README.md +3 -0
- package/apps/bridge/src/reply/cursor-agent-bin.ts +46 -0
- package/apps/bridge/src/reply/process.ts +3 -0
- package/apps/bridge/src/reply/provider-headless.ts +2 -1
- package/apps/bridge/src/reply/types.ts +3 -0
- package/apps/bridge/src/reply.ts +36 -10
- package/apps/bridge/src/session-keys.ts +16 -1
- package/apps/bridge/src/sessions/activity-helpers.ts +64 -5
- package/apps/bridge/src/sessions/capability-totals.ts +0 -0
- package/apps/bridge/src/sessions/claude.ts +5 -1
- package/apps/bridge/src/sessions/codex-head.ts +77 -0
- package/apps/bridge/src/sessions/codex.ts +26 -24
- package/apps/bridge/src/sessions/cursor/README.md +3 -0
- package/apps/bridge/src/sessions/cursor/transcripts.ts +5 -1
- package/apps/bridge/src/sessions/telemetry/README.md +3 -0
- package/apps/bridge/src/sessions/telemetry/identity.ts +1 -0
- package/apps/bridge/src/sessions/telemetry.ts +17 -0
- package/apps/bridge/src/sessions.ts +33 -6
- package/apps/bridge/src/tools/update-handler.ts +23 -0
- package/apps/bridge/src/tools/updater.ts +265 -0
- package/apps/bridge/src/tools/version.ts +113 -0
- package/apps/mcp/src/http-server.ts +3 -2
- package/apps/mcp/src/http-service/README.md +2 -0
- package/apps/mcp/src/http-service/common.ts +16 -1
- package/apps/mcp/src/mcp-tools/README.md +3 -0
- package/apps/mcp/src/mcp-tools/mesh-resource.ts +23 -0
- package/apps/mcp/src/oauth/README.md +2 -0
- package/apps/mcp/src/oauth/loopback-origin.ts +16 -0
- package/bin/granttap-mcp.mjs +2 -2
- package/cursor-plugin/.cursor-plugin/plugin.json +1 -1
- package/cursor-plugin/LICENSE +86 -0
- package/cursor-plugin/README.md +5 -0
- package/docs/images/iphone-chat.png +0 -0
- package/docs/images/iphone-claude-tasks.png +0 -0
- package/docs/images/iphone-command-center.png +0 -0
- package/docs/images/iphone-mcp-usage.png +0 -0
- package/package.json +4 -3
- package/packages/core/relay-client-types.ts +9 -0
- package/packages/core/relay-client.ts +50 -0
- package/packages/protocol/messages/README.md +12 -0
- package/packages/protocol/messages/capabilities.ts +54 -0
- package/packages/protocol/messages/interaction.ts +9 -0
- package/packages/protocol/messages/machine.ts +29 -0
- package/packages/protocol/messages/mesh.ts +53 -1
- package/packages/protocol/messages/project-policy.ts +172 -0
- package/packages/protocol/messages/sessions.ts +32 -0
- package/packages/protocol/schema.ts +16 -0
package/LICENSE
CHANGED
|
@@ -1,21 +1,86 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Serhii Ziborov
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
[](https://www.npmjs.com/package/granttap-mcp)
|
|
4
4
|
[](https://github.com/sergii-ziborov/granttap-mcp/actions/workflows/ci.yml)
|
|
5
|
-
[](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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
//
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
120
|
-
//
|
|
121
|
-
if (isUnanswered(decision))
|
|
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
|
}
|