@getmarrow/mcp 3.9.57 → 3.9.58
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 +9 -2
- package/dist/hook-context.d.ts +1 -1
- package/dist/hook-contract.d.ts +6 -6
- package/dist/hook-contract.js +1 -1
- package/dist/hook-session.d.ts +1 -1
- package/dist/hook.d.ts +1 -1
- package/package.json +1 -1
- package/scripts/control-path-canary.cjs +221 -81
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -104,9 +104,16 @@ npx -y @getmarrow/mcp@latest ping
|
|
|
104
104
|
|
|
105
105
|
Detection and notification are automatic. After explicit installer activation, the local controller may restore only Marrow-managed hooks/configuration. Package upgrades, owner policy, credentials, and unrelated configuration remain explicit and subject to the operator's normal change policy.
|
|
106
106
|
|
|
107
|
-
## What's New in v3.9.
|
|
107
|
+
## What's New in v3.9.58
|
|
108
108
|
|
|
109
|
-
v3.9.
|
|
109
|
+
v3.9.58 makes latency evidence accurate by reusing one initialized MCP process for the complete control-path canary:
|
|
110
|
+
|
|
111
|
+
- startup, initialization, and tool discovery are measured separately from authenticated tool calls;
|
|
112
|
+
- hot-path steering and report latency are reported as separate p50, p95, p99, and maximum groups;
|
|
113
|
+
- response IDs, malformed output, RPC failures, timeouts, and incomplete tool contracts fail closed;
|
|
114
|
+
- one bounded process prevents package and process startup time from being mistaken for API latency.
|
|
115
|
+
|
|
116
|
+
The compact agent control path introduced in v3.9.57 remains the default:
|
|
110
117
|
|
|
111
118
|
- `marrow_status`, `marrow_ask`, and `marrow_agent_runtime` use authenticated routes with bounded retries and typed failures;
|
|
112
119
|
- transient read failures return an owner-only last-known brief when available, while cached guidance can never authorize high-risk work;
|
package/dist/hook-context.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* disabled with `MARROW_AUTO_HOOK=false`.
|
|
14
14
|
*/
|
|
15
15
|
import type { MarrowAgentRuntimeResult, MarrowDecisionBriefResult, MarrowValueReportResult } from './types';
|
|
16
|
-
export declare const CONTEXT_HOOK_COMMAND = "npx -y @getmarrow/mcp@3.9.
|
|
16
|
+
export declare const CONTEXT_HOOK_COMMAND = "npx -y @getmarrow/mcp@3.9.58 context-hook";
|
|
17
17
|
interface InstallResult {
|
|
18
18
|
settingsPath: string;
|
|
19
19
|
installed: boolean;
|
package/dist/hook-contract.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export declare const MCP_ADAPTER_VERSION = "3.9.
|
|
1
|
+
export declare const MCP_ADAPTER_VERSION = "3.9.58";
|
|
2
2
|
export declare const NATIVE_HOOK_MATCHER = "Bash|Edit|Write|MultiEdit|mcp__(?!marrow__marrow_).*";
|
|
3
|
-
export declare const MCP_PACKAGE_SPEC = "@getmarrow/mcp@3.9.
|
|
4
|
-
export declare const CONTEXT_HOOK_COMMAND = "npx -y @getmarrow/mcp@3.9.
|
|
5
|
-
export declare const PRE_ACTION_HOOK_COMMAND = "npx -y @getmarrow/mcp@3.9.
|
|
6
|
-
export declare const ACTION_RESULT_HOOK_COMMAND = "npx -y @getmarrow/mcp@3.9.
|
|
7
|
-
export declare const SESSION_END_HOOK_COMMAND = "npx -y @getmarrow/mcp@3.9.
|
|
3
|
+
export declare const MCP_PACKAGE_SPEC = "@getmarrow/mcp@3.9.58";
|
|
4
|
+
export declare const CONTEXT_HOOK_COMMAND = "npx -y @getmarrow/mcp@3.9.58 context-hook";
|
|
5
|
+
export declare const PRE_ACTION_HOOK_COMMAND = "npx -y @getmarrow/mcp@3.9.58 pre-action-hook";
|
|
6
|
+
export declare const ACTION_RESULT_HOOK_COMMAND = "npx -y @getmarrow/mcp@3.9.58 hook";
|
|
7
|
+
export declare const SESSION_END_HOOK_COMMAND = "npx -y @getmarrow/mcp@3.9.58 session-hook";
|
|
8
8
|
export declare const NATIVE_EXPECTED_HOOKS: readonly ["prompt", "pre_action", "action_result", "session_end"];
|
|
9
9
|
type HookSettings = Record<string, unknown>;
|
|
10
10
|
export declare function findHookSettingsPath(startDir?: string): string;
|
package/dist/hook-contract.js
CHANGED
|
@@ -14,7 +14,7 @@ exports.stableSessionWorkflowId = stableSessionWorkflowId;
|
|
|
14
14
|
const node_crypto_1 = require("node:crypto");
|
|
15
15
|
const node_fs_1 = require("node:fs");
|
|
16
16
|
const node_path_1 = require("node:path");
|
|
17
|
-
exports.MCP_ADAPTER_VERSION = '3.9.
|
|
17
|
+
exports.MCP_ADAPTER_VERSION = '3.9.58';
|
|
18
18
|
exports.NATIVE_HOOK_MATCHER = 'Bash|Edit|Write|MultiEdit|mcp__(?!marrow__marrow_).*';
|
|
19
19
|
exports.MCP_PACKAGE_SPEC = `@getmarrow/mcp@${exports.MCP_ADAPTER_VERSION}`;
|
|
20
20
|
exports.CONTEXT_HOOK_COMMAND = `npx -y ${exports.MCP_PACKAGE_SPEC} context-hook`;
|
package/dist/hook-session.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const SESSION_HOOK_COMMAND = "npx -y @getmarrow/mcp@3.9.
|
|
1
|
+
export declare const SESSION_HOOK_COMMAND = "npx -y @getmarrow/mcp@3.9.58 session-hook";
|
|
2
2
|
export declare function installSessionEndHook(startDir?: string): {
|
|
3
3
|
settingsPath: string;
|
|
4
4
|
installed: boolean;
|
package/dist/hook.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const AUTO_HOOK_COMMAND = "npx -y @getmarrow/mcp@3.9.
|
|
1
|
+
export declare const AUTO_HOOK_COMMAND = "npx -y @getmarrow/mcp@3.9.58 hook";
|
|
2
2
|
export declare const AUTO_HOOK_MATCHER = "Bash|Edit|Write|MultiEdit|mcp__(?!marrow__marrow_).*";
|
|
3
3
|
interface HookEvent {
|
|
4
4
|
session_id?: string;
|
package/package.json
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const {
|
|
2
|
+
const { spawn } = require('node:child_process');
|
|
3
|
+
const { performance } = require('node:perf_hooks');
|
|
3
4
|
const { resolve } = require('node:path');
|
|
4
5
|
const { version: packageVersion } = require('../package.json');
|
|
5
6
|
|
|
6
|
-
const key = process.env.MARROW_API_KEY || '';
|
|
7
|
-
if (!key) {
|
|
8
|
-
process.stderr.write('MARROW_API_KEY is required for the authenticated MCP control-path canary.\n');
|
|
9
|
-
process.exit(2);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
7
|
const cases = [
|
|
13
8
|
['marrow_status', {}],
|
|
14
9
|
['marrow_runtime_status', { fast: true }],
|
|
@@ -22,19 +17,36 @@ const cases = [
|
|
|
22
17
|
['marrow_fleet_lessons', { query: 'safe release verification', limit: 3 }],
|
|
23
18
|
];
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
const HOT_PATH_TOOLS = new Set([
|
|
21
|
+
'marrow_status',
|
|
22
|
+
'marrow_runtime_status',
|
|
23
|
+
'marrow_orient',
|
|
24
|
+
'marrow_ask',
|
|
25
|
+
'marrow_agent_runtime',
|
|
26
|
+
]);
|
|
27
|
+
const OUTPUT_LIMIT_BYTES = 256 * 1024;
|
|
28
|
+
|
|
29
|
+
function boundedMs(name, fallback, minimum = 100, maximum = 10_000, env = process.env) {
|
|
30
|
+
const parsed = Number(env[name] || fallback);
|
|
27
31
|
return Number.isFinite(parsed) ? Math.max(minimum, Math.min(maximum, Math.floor(parsed))) : fallback;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
function percentile(values, quantile) {
|
|
35
|
+
if (!values.length) return null;
|
|
36
|
+
const ordered = [...values].sort((a, b) => a - b);
|
|
37
|
+
return ordered[Math.max(0, Math.ceil(quantile * ordered.length) - 1)];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function latencyGroup(rows) {
|
|
41
|
+
const values = rows.map((row) => row.latency_ms);
|
|
42
|
+
return {
|
|
43
|
+
count: values.length,
|
|
44
|
+
p50_ms: percentile(values, 0.5),
|
|
45
|
+
p95_ms: percentile(values, 0.95),
|
|
46
|
+
p99_ms: percentile(values, 0.99),
|
|
47
|
+
max_ms: values.length ? Math.max(...values) : null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
38
50
|
|
|
39
51
|
function toolPayload(message, name) {
|
|
40
52
|
if (!message) throw new Error(`${name} returned no MCP tool response`);
|
|
@@ -95,72 +107,200 @@ function validatePayload(name, payload) {
|
|
|
95
107
|
}
|
|
96
108
|
}
|
|
97
109
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
110
|
+
class PersistentMcpClient {
|
|
111
|
+
constructor(input) {
|
|
112
|
+
this.command = input.command;
|
|
113
|
+
this.args = input.args;
|
|
114
|
+
this.env = input.env;
|
|
115
|
+
this.timeoutMs = input.timeoutMs;
|
|
116
|
+
this.spawnProcess = input.spawnProcess || spawn;
|
|
117
|
+
this.maxOutputBytes = input.maxOutputBytes || OUTPUT_LIMIT_BYTES;
|
|
118
|
+
this.nextId = 1;
|
|
119
|
+
this.pending = new Map();
|
|
120
|
+
this.stdoutBuffer = '';
|
|
121
|
+
this.stderrBytes = 0;
|
|
122
|
+
this.closed = false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
start() {
|
|
126
|
+
if (this.child) return;
|
|
127
|
+
this.child = this.spawnProcess(this.command, this.args, {
|
|
128
|
+
env: this.env,
|
|
129
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
130
|
+
});
|
|
131
|
+
this.child.stdout.setEncoding('utf8');
|
|
132
|
+
this.child.stdout.on('data', (chunk) => this.onStdout(chunk));
|
|
133
|
+
this.child.stderr.on('data', (chunk) => {
|
|
134
|
+
this.stderrBytes += Buffer.byteLength(chunk);
|
|
135
|
+
if (this.stderrBytes > this.maxOutputBytes) this.abort(new Error('MCP child exceeded stderr limit'));
|
|
136
|
+
});
|
|
137
|
+
this.child.once('error', (error) => this.abort(new Error(`MCP child failed: ${error.code || 'process_error'}`)));
|
|
138
|
+
this.child.once('exit', (code, signal) => {
|
|
139
|
+
this.closed = true;
|
|
140
|
+
if (this.pending.size > 0) this.rejectAll(new Error(`MCP child exited ${code ?? signal ?? 'unknown'}`));
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
onStdout(chunk) {
|
|
145
|
+
this.stdoutBuffer += chunk;
|
|
146
|
+
if (Buffer.byteLength(this.stdoutBuffer) > this.maxOutputBytes) {
|
|
147
|
+
this.abort(new Error('MCP child exceeded stdout limit'));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
for (;;) {
|
|
151
|
+
const newline = this.stdoutBuffer.indexOf('\n');
|
|
152
|
+
if (newline < 0) break;
|
|
153
|
+
const line = this.stdoutBuffer.slice(0, newline).trim();
|
|
154
|
+
this.stdoutBuffer = this.stdoutBuffer.slice(newline + 1);
|
|
155
|
+
if (!line) continue;
|
|
156
|
+
let message;
|
|
157
|
+
try { message = JSON.parse(line); } catch {
|
|
158
|
+
this.abort(new Error('MCP child emitted malformed JSON'));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (!Number.isSafeInteger(message.id)) continue;
|
|
162
|
+
const pending = this.pending.get(message.id);
|
|
163
|
+
if (!pending) {
|
|
164
|
+
this.abort(new Error(`MCP child returned unexpected response id ${message.id}`));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
clearTimeout(pending.timer);
|
|
168
|
+
this.pending.delete(message.id);
|
|
169
|
+
pending.resolve(message);
|
|
170
|
+
}
|
|
124
171
|
}
|
|
125
|
-
if (child.status !== 0) throw new Error(`${name} MCP child exited ${child.status ?? 'unknown'}`);
|
|
126
|
-
const messages = String(child.stdout || '').trim().split('\n').filter(Boolean).map((line) => {
|
|
127
|
-
try { return JSON.parse(line); } catch { throw new Error(`${name} emitted non-JSON stdout`); }
|
|
128
|
-
});
|
|
129
|
-
const initialized = messages.find((message) => message.id === 1);
|
|
130
|
-
const listed = messages.find((message) => message.id === 2);
|
|
131
|
-
const called = messages.find((message) => message.id === 3);
|
|
132
|
-
const version = initialized?.result?.serverInfo?.version;
|
|
133
|
-
if (!version) throw new Error(`${name} MCP initialize produced no version`);
|
|
134
|
-
const names = new Set((listed?.result?.tools || []).map((tool) => tool.name));
|
|
135
|
-
if (!names.has(name)) throw new Error(`${name} is missing from the full MCP contract`);
|
|
136
|
-
const payload = toolPayload(called, name);
|
|
137
|
-
validatePayload(name, payload);
|
|
138
|
-
const live = payload.stale !== true && payload.source !== 'last_known' && payload.available !== false;
|
|
139
|
-
if (!live) throw new Error(`${name} returned cached or unavailable guidance`);
|
|
140
|
-
return { tool: name, ok: true, live: true, latency_ms: latencyMs, package_version: version };
|
|
141
|
-
}
|
|
142
172
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
173
|
+
request(method, params = {}, timeoutMs = this.timeoutMs) {
|
|
174
|
+
if (!this.child || this.closed || !this.child.stdin.writable) {
|
|
175
|
+
return Promise.reject(new Error('MCP child is not writable'));
|
|
176
|
+
}
|
|
177
|
+
const id = this.nextId++;
|
|
178
|
+
return new Promise((resolveRequest, rejectRequest) => {
|
|
179
|
+
const timer = setTimeout(() => {
|
|
180
|
+
this.pending.delete(id);
|
|
181
|
+
const error = new Error(`${method} timed out`);
|
|
182
|
+
rejectRequest(error);
|
|
183
|
+
this.abort(error);
|
|
184
|
+
}, timeoutMs);
|
|
185
|
+
this.pending.set(id, { resolve: resolveRequest, reject: rejectRequest, timer });
|
|
186
|
+
this.child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, method, params })}\n`, (error) => {
|
|
187
|
+
if (!error) return;
|
|
188
|
+
clearTimeout(timer);
|
|
189
|
+
this.pending.delete(id);
|
|
190
|
+
rejectRequest(new Error(`${method} write failed`));
|
|
191
|
+
this.abort(error);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
149
194
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
195
|
+
|
|
196
|
+
rejectAll(error) {
|
|
197
|
+
for (const pending of this.pending.values()) {
|
|
198
|
+
clearTimeout(pending.timer);
|
|
199
|
+
pending.reject(error);
|
|
200
|
+
}
|
|
201
|
+
this.pending.clear();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
abort(error) {
|
|
205
|
+
this.rejectAll(error instanceof Error ? error : new Error('MCP child aborted'));
|
|
206
|
+
if (this.child && !this.closed) this.child.kill('SIGKILL');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async stop() {
|
|
210
|
+
if (!this.child || this.closed) return;
|
|
211
|
+
this.child.stdin.end();
|
|
212
|
+
await new Promise((resolveExit) => {
|
|
213
|
+
const timer = setTimeout(() => {
|
|
214
|
+
if (!this.closed) this.child.kill('SIGKILL');
|
|
215
|
+
resolveExit();
|
|
216
|
+
}, 500);
|
|
217
|
+
this.child.once('exit', () => {
|
|
218
|
+
clearTimeout(timer);
|
|
219
|
+
resolveExit();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function runCanary(env = process.env, options = {}) {
|
|
226
|
+
const key = env.MARROW_API_KEY || '';
|
|
227
|
+
if (!key) throw new Error('MARROW_API_KEY is required for the authenticated MCP control-path canary');
|
|
228
|
+
const transportTimeoutMs = boundedMs('MARROW_REQUEST_TIMEOUT_MS', 2_500, 250, 10_000, env);
|
|
229
|
+
const totalTimeoutMs = boundedMs('MARROW_MCP_CANARY_TOTAL_TIMEOUT_MS', 30_000, 2_000, 60_000, env);
|
|
230
|
+
const expectedVersion = env.MARROW_EXPECTED_MCP_VERSION || packageVersion;
|
|
231
|
+
const childPath = env.MARROW_MCP_CANARY_CHILD || resolve(__dirname, '../dist/cli.js');
|
|
232
|
+
const childEnv = {
|
|
233
|
+
...env,
|
|
234
|
+
MARROW_API_KEY: key,
|
|
235
|
+
MARROW_BASE_URL: env.MARROW_BASE_URL || 'https://api.getmarrow.ai',
|
|
236
|
+
MARROW_AUTO_ENROLL: 'false',
|
|
237
|
+
MARROW_TOOL_PROFILE: 'full',
|
|
238
|
+
MARROW_CONTROL_PATH_CANARY: '1',
|
|
239
|
+
MARROW_REQUEST_TIMEOUT_MS: String(transportTimeoutMs),
|
|
240
|
+
};
|
|
241
|
+
delete childEnv.NODE_TEST_CONTEXT;
|
|
242
|
+
const client = new PersistentMcpClient({
|
|
243
|
+
command: process.execPath,
|
|
244
|
+
args: [childPath],
|
|
245
|
+
timeoutMs: transportTimeoutMs,
|
|
246
|
+
env: childEnv,
|
|
247
|
+
spawnProcess: options.spawnProcess,
|
|
248
|
+
});
|
|
249
|
+
const totalTimer = setTimeout(() => client.abort(new Error('MCP canary total timeout')), totalTimeoutMs);
|
|
250
|
+
const processStarted = performance.now();
|
|
251
|
+
client.start();
|
|
252
|
+
try {
|
|
253
|
+
const initialized = await client.request('initialize', {}, transportTimeoutMs + 3_000);
|
|
254
|
+
if (initialized.error) throw new Error(`MCP initialize failed ${initialized.error.code}`);
|
|
255
|
+
const version = initialized.result?.serverInfo?.version;
|
|
256
|
+
if (!version) throw new Error('MCP initialize produced no version');
|
|
257
|
+
if (version !== expectedVersion) throw new Error(`MCP canary loaded ${version} instead of expected ${expectedVersion}`);
|
|
258
|
+
const listed = await client.request('tools/list', {});
|
|
259
|
+
if (listed.error) throw new Error(`MCP tools/list failed ${listed.error.code}`);
|
|
260
|
+
const names = new Set((listed.result?.tools || []).map((tool) => tool.name));
|
|
261
|
+
for (const [name] of cases) {
|
|
262
|
+
if (!names.has(name)) throw new Error(`${name} is missing from the full MCP contract`);
|
|
263
|
+
}
|
|
264
|
+
const initializationMs = Math.round(performance.now() - processStarted);
|
|
265
|
+
const results = [];
|
|
266
|
+
for (const [name, args] of cases) {
|
|
267
|
+
const callStarted = performance.now();
|
|
268
|
+
const called = await client.request('tools/call', { name, arguments: args });
|
|
269
|
+
const latencyMs = Math.round(performance.now() - callStarted);
|
|
270
|
+
const payload = toolPayload(called, name);
|
|
271
|
+
validatePayload(name, payload);
|
|
272
|
+
const live = payload.stale !== true && payload.source !== 'last_known' && payload.available !== false;
|
|
273
|
+
if (!live) throw new Error(`${name} returned cached or unavailable guidance`);
|
|
274
|
+
results.push({ tool: name, ok: true, live: true, latency_ms: latencyMs });
|
|
275
|
+
}
|
|
276
|
+
const hotPath = results.filter((row) => HOT_PATH_TOOLS.has(row.tool));
|
|
277
|
+
const reports = results.filter((row) => !HOT_PATH_TOOLS.has(row.tool));
|
|
278
|
+
return {
|
|
279
|
+
ok: true,
|
|
280
|
+
package_version: version,
|
|
281
|
+
process_count: 1,
|
|
282
|
+
initialization_ms: initializationMs,
|
|
283
|
+
per_tool_latency_excludes_initialization: true,
|
|
284
|
+
tools_checked: results.length,
|
|
285
|
+
latency_groups: {
|
|
286
|
+
hot_path: latencyGroup(hotPath),
|
|
287
|
+
reports: latencyGroup(reports),
|
|
288
|
+
},
|
|
289
|
+
results,
|
|
290
|
+
};
|
|
291
|
+
} finally {
|
|
292
|
+
clearTimeout(totalTimer);
|
|
293
|
+
await client.stop();
|
|
154
294
|
}
|
|
155
|
-
process.stdout.write(`${JSON.stringify({
|
|
156
|
-
ok: true,
|
|
157
|
-
package_version: versions[0],
|
|
158
|
-
tools_checked: results.length,
|
|
159
|
-
core_max_ms: Math.max(...core.map((row) => row.latency_ms)),
|
|
160
|
-
latency_within_release_ceiling: !slow,
|
|
161
|
-
results: results.map(({ package_version, ...row }) => row),
|
|
162
|
-
})}\n`);
|
|
163
|
-
} catch (error) {
|
|
164
|
-
process.stderr.write(`MCP_CONTROL_PATH_CANARY=FAIL ${error instanceof Error ? error.message : 'unknown_error'}\n`);
|
|
165
|
-
process.exitCode = 1;
|
|
166
295
|
}
|
|
296
|
+
|
|
297
|
+
if (require.main === module) {
|
|
298
|
+
runCanary().then((result) => {
|
|
299
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
300
|
+
}).catch((error) => {
|
|
301
|
+
process.stderr.write(`MCP_CONTROL_PATH_CANARY=FAIL ${error instanceof Error ? error.message : 'unknown_error'}\n`);
|
|
302
|
+
process.exitCode = 1;
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
module.exports = { PersistentMcpClient, latencyGroup, percentile, runCanary };
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/getmarrow/marrow-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "3.9.
|
|
9
|
+
"version": "3.9.58",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@getmarrow/mcp",
|
|
14
|
-
"version": "3.9.
|
|
14
|
+
"version": "3.9.58",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|