@getmarrow/mcp 3.9.96 → 3.9.98
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/README.md +11 -1
- package/dist/cli.js +64 -1
- package/dist/cli.js.map +1 -1
- package/dist/hook-contract.d.ts +2 -2
- package/dist/hook-contract.js +1 -1
- package/dist/hook-pre-action.d.ts +48 -2
- package/dist/hook-pre-action.d.ts.map +1 -1
- package/dist/hook-pre-action.js +348 -88
- package/dist/hook-pre-action.js.map +1 -1
- package/dist/hook-tool-policy.d.ts +39 -0
- package/dist/hook-tool-policy.d.ts.map +1 -1
- package/dist/hook-tool-policy.js +620 -23
- package/dist/hook-tool-policy.js.map +1 -1
- package/dist/hook.d.ts +2 -0
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +14 -1
- package/dist/hook.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +69 -16
- package/dist/index.js.map +1 -1
- package/dist/lifecycle-spool.d.ts +25 -0
- package/dist/lifecycle-spool.d.ts.map +1 -1
- package/dist/lifecycle-spool.js +117 -19
- package/dist/lifecycle-spool.js.map +1 -1
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -2
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -136,7 +136,15 @@ v3.9.87 hardens the authenticated control-path canary against single-sample tran
|
|
|
136
136
|
|
|
137
137
|
v3.9.86 was published with the adapter version constant still at `3.9.85`, so the strict canary identity check rejected it; it is deprecated — use `3.9.87`. SDK `3.7.62` and installer `0.1.56` are unchanged; install MCP `3.9.87`, reload the host, review hook trust, and verify before claiming the updated client is active.
|
|
138
138
|
|
|
139
|
-
## What's New in v3.9.
|
|
139
|
+
## What's New in v3.9.98
|
|
140
|
+
|
|
141
|
+
v3.9.98 fixes `npx @getmarrow/mcp setup` and every other `npx @getmarrow/mcp …` command. Since 3.9.57 they failed with "could not determine executable to run", because the package had two executables and none named after the package. The package now also provides an executable named `mcp`, so those commands work again. `marrow-mcp` and `marrow-mcp-canary` are unchanged, and the `npx -y --package=@getmarrow/mcp@latest marrow-mcp …` commands shown in this README keep working. If you install the package globally (`npm install -g @getmarrow/mcp`), it now also adds a command named `mcp`, which may share a name with other tools; `npx` users are unaffected. Tools, hooks and policy behavior are unchanged.
|
|
142
|
+
|
|
143
|
+
### Previous release: v3.9.97
|
|
144
|
+
|
|
145
|
+
v3.9.97 makes native hook control easier to live with. Free and starter gates are advisory again, so an advisory gate no longer hard-blocks work; a `block` decision still denies on every plan. In Claude Code's default, acceptEdits and auto permission modes, an enforced review now shows an owner-approval prompt instead of a denial. In plan, dontAsk and bypassPermissions modes, and for arbitration reviews, the action is still denied. Read-only commands such as `git show`, `git log` and `ls` no longer trigger false blocks, and denial messages now say plainly why an action was stopped and what to do next. The hook sends `protocol_version` when it verifies an action permit. When an action is denied, the hook makes a best-effort attempt, capped at 2.5 s, to close the decision with a denied outcome; if the backend does not confirm, the decision stays open. Hooks return faster because telemetry is delivered by a detached background process that honors opt-outs and never blocks the action (see `MARROW_HOOK_BACKGROUND_NUDGE` in Environment). Pending writes are retried more reliably: the client honors `retry_after_ms` and the optional `lease_remaining_ms`, and resumes the same write instead of starting a new one (see `MARROW_WRITE_RECONCILIATION_BUDGET_MS`). Hook lifecycle receipts are much less likely to be dropped when many hook processes write the spool at once: each event takes the spool lock fewer times, and the lock wait is a 10 s time budget with randomized polling. A receipt can still be lost if the lock is held for the full 10 s. Policy decisions, proof requirements and fail-closed behavior are unchanged, except that an enforced review now asks the owner (in the modes above) instead of denying. Update, reload the host and review hook trust before relying on the new hook.
|
|
146
|
+
|
|
147
|
+
### Previous release: v3.9.96
|
|
140
148
|
|
|
141
149
|
v3.9.96 fixes native pre-action hooks that denied protected actions after the runtime gate allowed them. The hook sent `source_meta` fields that Think rejects, and without a risk level the runtime answered protected actions on its low-risk fast path, whose receipt cannot back an action permit. Protected actions now request a durable gate and create their decision with accepted metadata only; unprotected actions stop at the gate without creating a decision or permit. A rejected control call now names its HTTP status and failure code without echoing service text. Policy decisions, proof requirements and fail-closed behavior are unchanged. Update, reload the host and review hook trust before relying on the new hook.
|
|
142
150
|
|
|
@@ -672,6 +680,8 @@ See the [Trust Center](https://getmarrow.ai/trust/) for implemented controls, cu
|
|
|
672
680
|
| `MARROW_BASE_URL` | No | API base override |
|
|
673
681
|
| `MARROW_AGENT_ID` | No | Bound agent identity for MCP tools |
|
|
674
682
|
| `MARROW_FLEET_AGENT_ID` | No | Fleet agent identity used by passive setup |
|
|
683
|
+
| `MARROW_HOOK_BACKGROUND_NUDGE` | No | Default on: PostToolUse only spools the event and starts one detached background process that delivers it, so the hook adds almost no latency. Set to `false` to keep bounded inline delivery (750 ms) inside the hook. Never runs when `MARROW_AUTO_HOOK=false` or local control is disabled |
|
|
684
|
+
| `MARROW_WRITE_RECONCILIATION_BUDGET_MS` | No | Default `15000`, range 1000-60000: total time a pending think or commit may wait, resuming the same idempotency key and request hash after the server's `retry_after_ms` or `Retry-After`. When the next wait does not fit, the call returns the resumable pending receipt and never reports success |
|
|
675
685
|
|
|
676
686
|
## Documentation
|
|
677
687
|
|
package/dist/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ const hook_contract_1 = require("./hook-contract");
|
|
|
18
18
|
const hook_context_1 = require("./hook-context");
|
|
19
19
|
const hook_session_1 = require("./hook-session");
|
|
20
20
|
const hook_pre_action_1 = require("./hook-pre-action");
|
|
21
|
+
const node_child_process_1 = require("node:child_process");
|
|
21
22
|
const env_1 = require("./env");
|
|
22
23
|
const lifecycle_spool_1 = require("./lifecycle-spool");
|
|
23
24
|
const spool_command_1 = require("./spool-command");
|
|
@@ -61,6 +62,9 @@ function parseArgs() {
|
|
|
61
62
|
if (args[i] === 'drain-spool' || args[i] === '--drain-spool') {
|
|
62
63
|
result.drainSpool = true;
|
|
63
64
|
}
|
|
65
|
+
if (args[i] === 'background-nudge') {
|
|
66
|
+
result.backgroundNudge = true;
|
|
67
|
+
}
|
|
64
68
|
if (args[i] === 'ping' || args[i] === '--ping') {
|
|
65
69
|
result.ping = true;
|
|
66
70
|
}
|
|
@@ -81,6 +85,62 @@ function reportLifecycleSpool(input) {
|
|
|
81
85
|
}
|
|
82
86
|
return spool;
|
|
83
87
|
}
|
|
88
|
+
// PostToolUse only spools. When this invocation actually spooled an event (so it passed
|
|
89
|
+
// MARROW_AUTO_HOOK, local-control and read-only gates), start one detached, unref'd
|
|
90
|
+
// process that delivers with the longer background timeout. No spool scan happens here.
|
|
91
|
+
function launchBackgroundLifecycleNudge() {
|
|
92
|
+
try {
|
|
93
|
+
if (!(0, hook_1.hookSpooledLifecycleEvent)() || !(0, lifecycle_spool_1.backgroundNudgeEnabled)())
|
|
94
|
+
return;
|
|
95
|
+
const identity = (0, hook_contract_2.resolveNativeHookIdentity)(process.argv[2]);
|
|
96
|
+
const apiKey = identity.environment.apiKey || '';
|
|
97
|
+
if (!apiKey)
|
|
98
|
+
return;
|
|
99
|
+
const nonce = (0, lifecycle_spool_1.claimBackgroundNudgeLock)({ apiKey, agentId: identity.agent_id });
|
|
100
|
+
if (!nonce)
|
|
101
|
+
return;
|
|
102
|
+
const child = (0, node_child_process_1.spawn)(process.execPath, [process.argv[1], 'background-nudge'], {
|
|
103
|
+
detached: true, stdio: 'ignore', windowsHide: true,
|
|
104
|
+
env: { ...process.env, MARROW_INTERNAL_NUDGE_LOCK: nonce },
|
|
105
|
+
});
|
|
106
|
+
child.on('error', () => (0, lifecycle_spool_1.releaseBackgroundNudgeLock)({ apiKey, agentId: identity.agent_id, nonce }));
|
|
107
|
+
child.unref();
|
|
108
|
+
}
|
|
109
|
+
catch { /* the event stays spooled for the next nudge */ }
|
|
110
|
+
}
|
|
111
|
+
async function runBackgroundNudge() {
|
|
112
|
+
const identity = (0, hook_contract_2.resolveNativeHookIdentity)(undefined);
|
|
113
|
+
const apiKey = identity.environment.apiKey || '';
|
|
114
|
+
const agentId = identity.agent_id;
|
|
115
|
+
let nonce = process.env.MARROW_INTERNAL_NUDGE_LOCK || '';
|
|
116
|
+
const release = () => { if (apiKey && nonce)
|
|
117
|
+
(0, lifecycle_spool_1.releaseBackgroundNudgeLock)({ apiKey, agentId, nonce }); };
|
|
118
|
+
// Hard lifetime cap, below the lock's stale threshold.
|
|
119
|
+
const watchdog = setTimeout(() => { release(); process.exit(0); }, lifecycle_spool_1.BACKGROUND_NUDGE_MAX_LIFETIME_MS);
|
|
120
|
+
try {
|
|
121
|
+
if (!apiKey || !nonce)
|
|
122
|
+
return;
|
|
123
|
+
const baseUrl = (0, index_1.validateBaseUrl)(identity.environment.baseUrl || 'https://api.getmarrow.ai');
|
|
124
|
+
// Deliver, then re-check once for events spooled while this process held the lock.
|
|
125
|
+
for (let round = 0; round < 3; round += 1) {
|
|
126
|
+
await (0, lifecycle_spool_1.nudgeLifecycleSpool)({ apiKey, baseUrl, agentId });
|
|
127
|
+
if (!(0, lifecycle_spool_1.hasDueLifecycleEvents)({ apiKey, agentId }))
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
release();
|
|
131
|
+
const next = (0, lifecycle_spool_1.hasDueLifecycleEvents)({ apiKey, agentId }) ? (0, lifecycle_spool_1.claimBackgroundNudgeLock)({ apiKey, agentId }) : null;
|
|
132
|
+
if (next) {
|
|
133
|
+
nonce = next;
|
|
134
|
+
await (0, lifecycle_spool_1.nudgeLifecycleSpool)({ apiKey, baseUrl, agentId });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch { /* best effort */ }
|
|
138
|
+
finally {
|
|
139
|
+
clearTimeout(watchdog);
|
|
140
|
+
release();
|
|
141
|
+
process.exit(0);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
84
144
|
async function runPingCommand() {
|
|
85
145
|
if (cliArgs.apiKey) {
|
|
86
146
|
process.stderr.write('Error: ping requires MARROW_API_KEY from trusted environment or owner configuration; --key is not accepted.\n');
|
|
@@ -365,7 +425,10 @@ if (process.argv[2] === 'keys') {
|
|
|
365
425
|
// Only start MCP server if not handling a CLI command
|
|
366
426
|
if (process.argv[2] !== 'keys') {
|
|
367
427
|
if (cliArgs.hook) {
|
|
368
|
-
void (0, hook_1.runHookCommand)();
|
|
428
|
+
void (0, hook_1.runHookCommand)().finally(launchBackgroundLifecycleNudge);
|
|
429
|
+
}
|
|
430
|
+
else if (cliArgs.backgroundNudge) {
|
|
431
|
+
void runBackgroundNudge();
|
|
369
432
|
}
|
|
370
433
|
else if (cliArgs.contextHook) {
|
|
371
434
|
void (0, hook_context_1.runContextHookCommand)();
|