granttap-mcp 0.7.1 → 0.8.1
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 +43 -11
- package/SECURITY.md +33 -0
- package/THIRD_PARTY_NOTICES.md +23 -0
- package/apps/bridge/src/bin/claude-hook.ts +66 -48
- package/apps/bridge/src/bin/codex-hook.ts +24 -5
- package/apps/bridge/src/bin/codex-policy-hook.ts +36 -4
- package/apps/bridge/src/bin/cursor-hook.ts +69 -14
- package/apps/bridge/src/bin/cursor-mcp-hook.ts +104 -31
- 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/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 +49 -25
- package/apps/bridge/src/launchd-safety.ts +53 -0
- 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 +29 -4
- package/apps/bridge/src/mesh/binding-state.ts +87 -0
- package/apps/bridge/src/mesh/call-scope.ts +1 -1
- package/apps/bridge/src/mesh/capsule.ts +2 -0
- package/apps/bridge/src/mesh/catalog.ts +111 -21
- package/apps/bridge/src/mesh/convergence.ts +84 -0
- package/apps/bridge/src/mesh/execution-sweep.ts +35 -0
- package/apps/bridge/src/mesh/identity.ts +21 -4
- package/apps/bridge/src/mesh/readiness.ts +19 -2
- package/apps/bridge/src/mesh/snapshot-merge.ts +29 -0
- package/apps/bridge/src/mesh/store-state.ts +90 -0
- package/apps/bridge/src/mesh/store-support.ts +15 -0
- package/apps/bridge/src/mesh/store.ts +104 -97
- package/apps/bridge/src/mesh/task-state.ts +106 -0
- package/apps/bridge/src/monitor-leadership.ts +139 -0
- package/apps/bridge/src/monitor.ts +36 -72
- 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/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/provider-headless.ts +2 -1
- package/apps/bridge/src/reply/types.ts +3 -0
- package/apps/bridge/src/reply.ts +24 -5
- package/apps/bridge/src/self-protection.ts +149 -0
- package/apps/bridge/src/session-keys.ts +16 -1
- package/apps/bridge/src/sessions/activity-helpers.ts +2 -1
- package/apps/bridge/src/sessions/capability-totals.ts +0 -0
- package/apps/bridge/src/sessions/claude.ts +1 -1
- package/apps/bridge/src/sessions/codex-head.ts +77 -0
- package/apps/bridge/src/sessions/codex.ts +22 -24
- package/apps/bridge/src/sessions/cursor/README.md +3 -0
- package/apps/bridge/src/sessions/cursor/transcripts.ts +1 -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 +17 -5
- package/apps/mcp/src/create-server.ts +1 -1
- 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/installer.ts +3 -0
- package/apps/mcp/src/http-service/snapshot.ts +3 -2
- package/apps/mcp/src/mcp-tools/README.md +3 -0
- package/apps/mcp/src/mcp-tools/interaction.ts +26 -7
- package/apps/mcp/src/mcp-tools/relay.ts +29 -15
- package/apps/mcp/src/mesh-server.ts +1 -1
- package/apps/mcp/src/oauth/README.md +2 -0
- package/apps/mcp/src/oauth/loopback-origin.ts +16 -0
- package/bin/granttap-mcp.mjs +1 -1
- 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 +6 -5
- package/packages/protocol/messages/README.md +12 -0
- package/packages/protocol/messages/approvals.ts +3 -0
- package/packages/protocol/messages/capabilities.ts +54 -0
- package/packages/protocol/messages/interaction.ts +13 -0
- package/packages/protocol/messages/machine.ts +29 -0
- package/packages/protocol/messages/mesh.ts +31 -1
- package/packages/protocol/messages/project-policy.ts +172 -0
- package/packages/protocol/schema.ts +12 -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)
|
|
@@ -51,12 +55,11 @@ Mesh resource. Full transcripts and hidden reasoning are never mesh payloads.
|
|
|
51
55
|
Claims have TTLs; a colliding claim is rejected before it is recorded so agents
|
|
52
56
|
can choose different work or contact the owner before escalating to Needs You.
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
data: an attributed call returns this execution's opaque
|
|
58
|
+
For Claude Code, Codex, and Cursor, the provider hook runs inside the agent's
|
|
59
|
+
own session and sees the exact call. GrantTap attributes every `notify` to the
|
|
60
|
+
session that really made it and publishes the event only for that execution —
|
|
61
|
+
a model that learned another session's id cannot act in its name.
|
|
62
|
+
`granttap://mesh/current` therefore carries no Project data: an attributed call returns this execution's opaque
|
|
60
63
|
`granttap://mesh/<capability>` URI, and only that URI serves its Project, Task,
|
|
61
64
|
owners, claims, dependencies, and relevant events. Ownership transfer stays out
|
|
62
65
|
of the tool surface entirely: `HANDOFF_ACCEPTED` and `HANDOFF_REJECTED` are
|
|
@@ -66,8 +69,11 @@ The first executable handoff path is Claude Code ↔ Codex across linked
|
|
|
66
69
|
computers. GrantTap builds a bounded Task Capsule from explicit task/git facts,
|
|
67
70
|
requires local phone authorization, creates a separate target branch/worktree,
|
|
68
71
|
starts the target execution, and returns a receipt bound to the exact capsule.
|
|
69
|
-
Cursor
|
|
70
|
-
|
|
72
|
+
Cursor uses the same provider-neutral schema and trusted caller attribution.
|
|
73
|
+
Grok Build remains Experimental and observable where available, but does not
|
|
74
|
+
yet expose a trusted caller hook, so agent-authored scoped Mesh events are not
|
|
75
|
+
offered for it. Unsupported remote-start paths fail closed instead of claiming
|
|
76
|
+
parity.
|
|
71
77
|
|
|
72
78
|
A capsule carries facts, not files. A handoff from a checkout with uncommitted
|
|
73
79
|
changes is refused — "This task has uncommitted changes. Commit or checkpoint
|
|
@@ -95,6 +101,9 @@ history stays on the device.
|
|
|
95
101
|
|
|
96
102
|
## Install
|
|
97
103
|
|
|
104
|
+
Installation and production use require Authorized Access under the
|
|
105
|
+
[GrantTap Commercial Source License](LICENSE).
|
|
106
|
+
|
|
98
107
|
```bash
|
|
99
108
|
npm install -g granttap-mcp
|
|
100
109
|
granttap setup
|
|
@@ -105,10 +114,17 @@ hooks, installs the background helper, configures Cursor's persistent local
|
|
|
105
114
|
OAuth service when Cursor is present, and starts phone pairing when run in an
|
|
106
115
|
interactive terminal. It ends with one exact next action.
|
|
107
116
|
|
|
117
|
+
Setup also declares the separately distributed GrantTap Engine, which Project
|
|
118
|
+
Governance needs before it can report anything. The standard locations are
|
|
119
|
+
searched, and `--engine <path>` points at one that lives elsewhere; the binary
|
|
120
|
+
is checksummed here, because that checksum is the only thing verified before it
|
|
121
|
+
is launched. Without an engine the rollout stays off and setup says so, rather
|
|
122
|
+
than leaving "Governance not reported" on the phone as the only symptom.
|
|
123
|
+
|
|
108
124
|
The normal CLI surface is intentionally small:
|
|
109
125
|
|
|
110
126
|
```text
|
|
111
|
-
granttap setup
|
|
127
|
+
granttap setup [--engine <path>]
|
|
112
128
|
granttap status [--json]
|
|
113
129
|
granttap connect [--relay <wss-url>]
|
|
114
130
|
granttap reset [--yes]
|
|
@@ -160,11 +176,19 @@ The bounded encrypted protocol preserves:
|
|
|
160
176
|
- visible activity, delivery state, context and token counters;
|
|
161
177
|
- MCP, Skill, and CLI observations;
|
|
162
178
|
- child-agent relationships;
|
|
163
|
-
- per-capability outcome: `success`, `error`, `cancelled`, or `unknown
|
|
179
|
+
- per-capability outcome: `success`, `error`, `cancelled`, or `unknown`;
|
|
180
|
+
- what a call cost, where the machine can be observed while it ran.
|
|
164
181
|
|
|
165
182
|
An optional bounded `errorClass` may describe an error category. Full tool
|
|
166
183
|
error payloads are not copied into usage telemetry by default.
|
|
167
184
|
|
|
185
|
+
Cost is reported as attributed rather than measured, because that is what it
|
|
186
|
+
is. A call is read back from the transcript once it has finished, so it can
|
|
187
|
+
never be measured directly: an MCP server outlives its calls and is sampled
|
|
188
|
+
directly, while a built-in tool leaves nothing behind and is costed from the
|
|
189
|
+
samples that fall inside its own start and end. A call with no sample near it
|
|
190
|
+
reports nothing rather than a number borrowed from another moment.
|
|
191
|
+
|
|
168
192
|
## Local enforcement
|
|
169
193
|
|
|
170
194
|
GrantTap can narrow later actions for an exact task only where a provider
|
|
@@ -215,4 +239,12 @@ gate on macOS, and `npm publish` enforces the same gate through
|
|
|
215
239
|
Do not publish from a dirty checkout or before the package allowlist, tests,
|
|
216
240
|
typecheck, and release checks pass.
|
|
217
241
|
|
|
242
|
+
## License
|
|
243
|
+
|
|
244
|
+
GrantTap MCP is distributed under the proprietary
|
|
245
|
+
[GrantTap Commercial Source License 1.0](LICENSE). Public source access does not
|
|
246
|
+
grant open-source, redistribution, hosted-service, or competing-product rights.
|
|
247
|
+
Third-party dependencies retain their own terms; see
|
|
248
|
+
[THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
|
249
|
+
|
|
218
250
|
GrantTap is not affiliated with Anthropic, OpenAI, Apple, Anysphere, or xAI.
|
package/SECURITY.md
CHANGED
|
@@ -20,6 +20,24 @@ credential used for authenticated WebSocket and APNs registration.
|
|
|
20
20
|
Provider credentials remain in each provider's own local authentication flow.
|
|
21
21
|
GrantTap does not send them to the relay.
|
|
22
22
|
|
|
23
|
+
Installed Claude Code, Codex, and Cursor hooks deny direct agent tool calls
|
|
24
|
+
that name GrantTap's local pairing, key, or policy files, including calls that
|
|
25
|
+
would otherwise be auto-accepted or run in provider bypass mode. The denial
|
|
26
|
+
covers the config directory itself, any glob over it, and every file not on a
|
|
27
|
+
short access-mode list, so a file added later is protected the day it appears.
|
|
28
|
+
Only helper logs and bounded runtime diagnostics are agent-readable, and they
|
|
29
|
+
remain read-only. Raw Mesh state and hook-attribution records are never exposed
|
|
30
|
+
to agent tools; `workspaces/` and `worktrees/` remain normal editable agent
|
|
31
|
+
workspace. The guard denies only what it positively recognised as a path into
|
|
32
|
+
that directory, and a fault inside the guard itself is printed and allowed
|
|
33
|
+
through instead of blocking every tool call on the machine: an outage of every
|
|
34
|
+
agent is a worse failure than the file it was watching. This is
|
|
35
|
+
best-effort self-protection, not an operating-system security boundary: a
|
|
36
|
+
process running as the same macOS user can use an unobserved tool, shell
|
|
37
|
+
obfuscation, or direct filesystem access outside those hooks. Protect the user
|
|
38
|
+
account and disk; use a trusted terminal for intentional maintenance. Moving
|
|
39
|
+
remaining local secret material into Keychain is the stronger future boundary.
|
|
40
|
+
|
|
23
41
|
## Cryptographic boundary
|
|
24
42
|
|
|
25
43
|
- Every computer pairing is generated locally with unique Curve25519 keys.
|
|
@@ -67,6 +85,10 @@ acceptance includes a SHA-256 receipt over a canonical capsule and the source,
|
|
|
67
85
|
target, Task identity, and acceptance time. Stale claims expire, replayed event
|
|
68
86
|
IDs are ignored, and destination routing is explicit.
|
|
69
87
|
|
|
88
|
+
Repository remotes included in Project snapshots are reduced to lowercase
|
|
89
|
+
host/path form. Userinfo, passwords or tokens, query strings, fragments, and
|
|
90
|
+
local-path remotes are never persisted as `baseRemote`.
|
|
91
|
+
|
|
70
92
|
## Runtime controls
|
|
71
93
|
|
|
72
94
|
Provider hooks enforce policy on the computer. Global provider deny always
|
|
@@ -87,5 +109,16 @@ expiry, actor, Project scope, and operation, and a revoked or disabled endpoint
|
|
|
87
109
|
fails closed. Invite creation, actor enablement, Project scope, and revocation
|
|
88
110
|
stay in the iPhone UI and the trusted CLI, never in a model-callable tool.
|
|
89
111
|
|
|
112
|
+
## Local helper installation
|
|
113
|
+
|
|
114
|
+
The background helper is a user LaunchAgent, and `launchctl bootstrap` replaces
|
|
115
|
+
whatever job already holds that label. GrantTap therefore refuses to load a
|
|
116
|
+
LaunchAgent whose plist lives under the operating system's temporary directory:
|
|
117
|
+
a test, probe, or agent run with a temporary `HOME` must never evict the
|
|
118
|
+
installed helper and leave launchd restarting a program whose files were already
|
|
119
|
+
cleaned up. Only `GRANTTAP_TEST_FAKE_LAUNCHCTL=1`, which states that `launchctl`
|
|
120
|
+
on `PATH` is a stub, lifts that refusal.
|
|
121
|
+
|
|
122
|
+
|
|
90
123
|
Relay vulnerabilities should be reported through the
|
|
91
124
|
[GrantTap relay advisory form](https://github.com/sergii-ziborov/granttap-relay/security/advisories/new).
|
|
@@ -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.
|
|
@@ -25,6 +25,11 @@ import {
|
|
|
25
25
|
} from "../config";
|
|
26
26
|
import { recordAttributedCall } from "../mesh/call-scope";
|
|
27
27
|
import { classifyAction } from "../policy";
|
|
28
|
+
import {
|
|
29
|
+
evaluateEffectiveAction,
|
|
30
|
+
legacyGrantTapFlowAllowed,
|
|
31
|
+
} from "../policy/effective-action";
|
|
32
|
+
import { protectedGrantTapAccess } from "../self-protection";
|
|
28
33
|
|
|
29
34
|
async function readStdin(): Promise<string> {
|
|
30
35
|
const chunks: Buffer[] = [];
|
|
@@ -34,19 +39,20 @@ async function readStdin(): Promise<string> {
|
|
|
34
39
|
|
|
35
40
|
async function main(): Promise<void> {
|
|
36
41
|
const raw = await readStdin();
|
|
37
|
-
let input: HookInput
|
|
42
|
+
let input: HookInput;
|
|
38
43
|
try {
|
|
39
44
|
input = JSON.parse(raw) as HookInput;
|
|
40
45
|
} catch {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
writePermission("deny", "GrantTap received invalid hook JSON");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
await handlePreToolUse(input);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function handlePreToolUse(input: HookInput): Promise<void> {
|
|
53
|
+
const protectedAccess = protectedGrantTapAccess(input.tool_name, input.tool_input);
|
|
54
|
+
if (protectedAccess) {
|
|
55
|
+
writePermission("deny", protectedAccess.reason);
|
|
50
56
|
return;
|
|
51
57
|
}
|
|
52
58
|
|
|
@@ -67,43 +73,42 @@ async function main(): Promise<void> {
|
|
|
67
73
|
input.tool_name,
|
|
68
74
|
input.tool_input,
|
|
69
75
|
);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
76
|
+
const projectDecision = await evaluateEffectiveAction({
|
|
77
|
+
provider: "claude",
|
|
78
|
+
sessionId: input.session_id,
|
|
79
|
+
cwd: input.cwd,
|
|
80
|
+
toolName: input.tool_name,
|
|
81
|
+
toolInput: input.tool_input,
|
|
82
|
+
legacyDenyReason: blocked?.reason,
|
|
83
|
+
});
|
|
84
|
+
if (projectDecision.effect === "deny") {
|
|
85
|
+
writePermission("deny", projectDecision.reason);
|
|
80
86
|
return;
|
|
81
87
|
}
|
|
88
|
+
const legacyFlow = legacyGrantTapFlowAllowed(projectDecision);
|
|
82
89
|
|
|
83
90
|
// Claude already granted this chat unconditional tool access. GrantTap's
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
if (input.permission_mode === "bypassPermissions") return;
|
|
91
|
+
// Project ASK/DENY is a parent boundary, so bypass mode cannot weaken it.
|
|
92
|
+
if (legacyFlow && input.permission_mode === "bypassPermissions") return;
|
|
87
93
|
|
|
88
94
|
// Gating paused, or this session is exempt → abstain (empty output = Claude
|
|
89
95
|
// uses its normal permission flow, exactly as if GrantTap weren't installed).
|
|
90
|
-
if (isGatingSkipped(input.session_id)) return;
|
|
96
|
+
if (legacyFlow && isGatingSkipped(input.session_id)) return;
|
|
97
|
+
await continuePermissionFlow(input, legacyFlow);
|
|
98
|
+
}
|
|
91
99
|
|
|
100
|
+
async function continuePermissionFlow(input: HookInput, legacyFlow: boolean): Promise<void> {
|
|
92
101
|
let cfg;
|
|
93
102
|
try {
|
|
94
103
|
cfg = loadConfig(machineConfigPath());
|
|
95
104
|
} catch {
|
|
105
|
+
if (!legacyFlow) {
|
|
106
|
+
writePermission("deny", "Project policy requires GrantTap approval, but this computer is not paired");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
96
109
|
// No pairing yet: don't block the user's normal workflow — defer to Claude's
|
|
97
110
|
// own prompt by emitting "ask".
|
|
98
|
-
|
|
99
|
-
JSON.stringify({
|
|
100
|
-
hookSpecificOutput: {
|
|
101
|
-
hookEventName: "PreToolUse",
|
|
102
|
-
permissionDecision: "ask",
|
|
103
|
-
permissionDecisionReason: "GrantTap not paired (run `npm run init`)",
|
|
104
|
-
},
|
|
105
|
-
}),
|
|
106
|
-
);
|
|
111
|
+
writePermission("ask", "GrantTap not paired (run `npm run init`)");
|
|
107
112
|
return;
|
|
108
113
|
}
|
|
109
114
|
|
|
@@ -112,21 +117,8 @@ async function main(): Promise<void> {
|
|
|
112
117
|
// the user never finished setting up. Once paired, this is what keeps routine
|
|
113
118
|
// work independent of whether the phone is awake, reachable, or even running.
|
|
114
119
|
const req = claudeToRequest(input);
|
|
115
|
-
if (shouldAutoAcceptTool(input.session_id, req.tool, req.command)) {
|
|
116
|
-
|
|
117
|
-
const cls = classifyAction(req.tool, req.command);
|
|
118
|
-
process.stdout.write(
|
|
119
|
-
JSON.stringify(
|
|
120
|
-
decisionToClaudeOutput({
|
|
121
|
-
type: "approval.decision",
|
|
122
|
-
requestId: req.requestId,
|
|
123
|
-
decision: "allow",
|
|
124
|
-
decidedBy: "auto",
|
|
125
|
-
note: `GrantTap auto-accept (${level} / ${cls})`,
|
|
126
|
-
decidedAt: Date.now(),
|
|
127
|
-
}),
|
|
128
|
-
),
|
|
129
|
-
);
|
|
120
|
+
if (legacyFlow && shouldAutoAcceptTool(input.session_id, req.tool, req.command)) {
|
|
121
|
+
writeAutoApproval(input, req);
|
|
130
122
|
return;
|
|
131
123
|
}
|
|
132
124
|
|
|
@@ -140,12 +132,38 @@ async function main(): Promise<void> {
|
|
|
140
132
|
// Relay down or phone asleep ≠ phone said no: abstain so Claude's local flow
|
|
141
133
|
// handles it (avoid flooding "решай локально" on every tool).
|
|
142
134
|
if (isUnanswered(decision)) {
|
|
135
|
+
if (!legacyFlow) {
|
|
136
|
+
writePermission("deny", "Project approval was required but no decision was received");
|
|
137
|
+
}
|
|
143
138
|
return;
|
|
144
139
|
}
|
|
145
140
|
|
|
146
141
|
process.stdout.write(JSON.stringify(decisionToClaudeOutput(decision)));
|
|
147
142
|
}
|
|
148
143
|
|
|
144
|
+
function writeAutoApproval(input: HookInput, req: ReturnType<typeof claudeToRequest>): void {
|
|
145
|
+
const level = autoAcceptLevelFor(input.session_id);
|
|
146
|
+
const cls = classifyAction(req.tool, req.command);
|
|
147
|
+
process.stdout.write(JSON.stringify(decisionToClaudeOutput({
|
|
148
|
+
type: "approval.decision",
|
|
149
|
+
requestId: req.requestId,
|
|
150
|
+
decision: "allow",
|
|
151
|
+
decidedBy: "auto",
|
|
152
|
+
note: `GrantTap auto-accept (${level} / ${cls})`,
|
|
153
|
+
decidedAt: Date.now(),
|
|
154
|
+
})));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function writePermission(decision: "deny" | "ask", reason: string): void {
|
|
158
|
+
process.stdout.write(JSON.stringify({
|
|
159
|
+
hookSpecificOutput: {
|
|
160
|
+
hookEventName: "PreToolUse",
|
|
161
|
+
permissionDecision: decision,
|
|
162
|
+
permissionDecisionReason: reason,
|
|
163
|
+
},
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
|
|
149
167
|
main().catch((err) => {
|
|
150
168
|
process.stdout.write(
|
|
151
169
|
JSON.stringify({
|
|
@@ -25,6 +25,8 @@ 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";
|
|
29
|
+
import { protectedGrantTapAccess } from "../self-protection";
|
|
28
30
|
|
|
29
31
|
async function readStdin(): Promise<string> {
|
|
30
32
|
const chunks: Buffer[] = [];
|
|
@@ -51,6 +53,12 @@ async function main(): Promise<void> {
|
|
|
51
53
|
return;
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
const protectedAccess = protectedGrantTapAccess(input.tool_name, input.tool_input);
|
|
57
|
+
if (protectedAccess) {
|
|
58
|
+
process.stdout.write(denyOutput(protectedAccess.reason));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
if (!isProviderEnabled("codex")) return;
|
|
55
63
|
|
|
56
64
|
// See claude-hook.ts: the MCP server trusts this attribution, never the model.
|
|
@@ -70,9 +78,15 @@ async function main(): Promise<void> {
|
|
|
70
78
|
process.stdout.write(denyOutput(blocked.reason));
|
|
71
79
|
return;
|
|
72
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
|
+
});
|
|
73
87
|
|
|
74
88
|
// Gating paused or this session exempt → stay silent (Codex uses its own flow).
|
|
75
|
-
if (isGatingSkipped(input.session_id)) return;
|
|
89
|
+
if (!projectAsk && isGatingSkipped(input.session_id)) return;
|
|
76
90
|
|
|
77
91
|
let cfg;
|
|
78
92
|
try {
|
|
@@ -84,7 +98,7 @@ async function main(): Promise<void> {
|
|
|
84
98
|
|
|
85
99
|
// Local policy, evaluated after pairing is confirmed — see claude-hook.
|
|
86
100
|
const req = codexToRequest(input);
|
|
87
|
-
if (shouldAutoAcceptTool(input.session_id, req.tool, req.command)) {
|
|
101
|
+
if (!projectAsk && shouldAutoAcceptTool(input.session_id, req.tool, req.command)) {
|
|
88
102
|
const level = autoAcceptLevelFor(input.session_id);
|
|
89
103
|
const cls = classifyAction(req.tool, req.command);
|
|
90
104
|
process.stdout.write(
|
|
@@ -109,9 +123,14 @@ async function main(): Promise<void> {
|
|
|
109
123
|
);
|
|
110
124
|
const decision = await requestApproval(cfg, req, { timeoutMs });
|
|
111
125
|
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
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
|
+
}
|
|
115
134
|
|
|
116
135
|
process.stdout.write(JSON.stringify(decisionToCodexOutput(decision)));
|
|
117
136
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
|
-
/**
|
|
2
|
+
/** Codex PreToolUse: deterministic deny or one-shot Project ASK handoff. */
|
|
3
3
|
import type { HookInput } from "../adapters";
|
|
4
4
|
import { blockedSessionCapability, isProviderEnabled } from "../config";
|
|
5
|
+
import { recordCodexProjectAsk } from "../policy/codex-project-ask";
|
|
6
|
+
import { evaluateEffectiveAction } from "../policy/effective-action";
|
|
7
|
+
import { protectedGrantTapAccess } from "../self-protection";
|
|
5
8
|
|
|
6
9
|
async function readStdin(): Promise<string> {
|
|
7
10
|
const chunks: Buffer[] = [];
|
|
@@ -10,24 +13,53 @@ async function readStdin(): Promise<string> {
|
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
async function main(): Promise<void> {
|
|
13
|
-
if (!isProviderEnabled("codex")) return;
|
|
14
16
|
let input: HookInput;
|
|
15
17
|
try {
|
|
16
18
|
input = JSON.parse(await readStdin()) as HookInput;
|
|
17
19
|
} catch {
|
|
18
20
|
return;
|
|
19
21
|
}
|
|
22
|
+
const protectedAccess = protectedGrantTapAccess(input.tool_name, input.tool_input);
|
|
23
|
+
if (protectedAccess) {
|
|
24
|
+
deny(protectedAccess.reason);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (!isProviderEnabled("codex")) return;
|
|
20
28
|
const blocked = blockedSessionCapability(
|
|
21
29
|
input.session_id,
|
|
22
30
|
input.tool_name,
|
|
23
31
|
input.tool_input,
|
|
24
32
|
);
|
|
25
|
-
|
|
33
|
+
const decision = await evaluateEffectiveAction({
|
|
34
|
+
provider: "codex",
|
|
35
|
+
sessionId: input.session_id,
|
|
36
|
+
cwd: input.cwd,
|
|
37
|
+
toolName: input.tool_name,
|
|
38
|
+
toolInput: input.tool_input,
|
|
39
|
+
legacyDenyReason: blocked?.reason,
|
|
40
|
+
});
|
|
41
|
+
if (decision.effect === "deny") {
|
|
42
|
+
deny(decision.reason);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (decision.effect !== "ask") return;
|
|
46
|
+
const recorded = recordCodexProjectAsk({
|
|
47
|
+
sessionId: input.session_id,
|
|
48
|
+
toolUseId: input.tool_use_id,
|
|
49
|
+
toolName: input.tool_name,
|
|
50
|
+
toolInput: input.tool_input,
|
|
51
|
+
}, decision.reason);
|
|
52
|
+
if (!recorded) {
|
|
53
|
+
deny("Project approval could not be bound to this exact Codex call");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function deny(reason: string): void {
|
|
26
58
|
process.stdout.write(JSON.stringify({
|
|
27
59
|
hookSpecificOutput: {
|
|
28
60
|
hookEventName: "PreToolUse",
|
|
29
61
|
permissionDecision: "deny",
|
|
30
|
-
permissionDecisionReason:
|
|
62
|
+
permissionDecisionReason: reason,
|
|
31
63
|
},
|
|
32
64
|
}));
|
|
33
65
|
}
|