@getmarrow/mcp 3.9.56 → 3.9.57
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 +14 -2
- package/dist/cli.js +244 -109
- package/dist/cli.js.map +1 -1
- package/dist/hook-context.d.ts +2 -1
- package/dist/hook-context.d.ts.map +1 -1
- package/dist/hook-context.js +2 -1
- package/dist/hook-context.js.map +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/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -15
- package/dist/index.js.map +1 -1
- package/dist/redact.js +1 -1
- package/dist/redact.js.map +1 -1
- package/dist/request-reliability.d.ts +27 -0
- package/dist/request-reliability.d.ts.map +1 -0
- package/dist/request-reliability.js +264 -0
- package/dist/request-reliability.js.map +1 -0
- package/dist/runtime-contract.d.ts +5 -0
- package/dist/runtime-contract.d.ts.map +1 -0
- package/dist/runtime-contract.js +107 -0
- package/dist/runtime-contract.js.map +1 -0
- package/package.json +7 -4
- package/scripts/control-path-canary.cjs +166 -0
- package/server.json +2 -2
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isValidRuntimeResult = isValidRuntimeResult;
|
|
4
|
+
exports.normalizeRuntimeResult = normalizeRuntimeResult;
|
|
5
|
+
exports.highRiskRuntimeCanClose = highRiskRuntimeCanClose;
|
|
6
|
+
const RUNTIME_GATE_DECISIONS = new Set([
|
|
7
|
+
'allow',
|
|
8
|
+
'proceed',
|
|
9
|
+
'warn',
|
|
10
|
+
'review_required',
|
|
11
|
+
'owner_approval_required',
|
|
12
|
+
'block',
|
|
13
|
+
'deny',
|
|
14
|
+
'denied',
|
|
15
|
+
]);
|
|
16
|
+
const RUNTIME_RISK_LEVELS = new Set(['low', 'medium', 'high', 'critical']);
|
|
17
|
+
function isValidRuntimeResult(value) {
|
|
18
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
19
|
+
return false;
|
|
20
|
+
const riskGate = value.risk_gate;
|
|
21
|
+
if (!riskGate || typeof riskGate !== 'object' || Array.isArray(riskGate))
|
|
22
|
+
return false;
|
|
23
|
+
const gate = riskGate;
|
|
24
|
+
return typeof gate.allow === 'boolean'
|
|
25
|
+
&& typeof gate.decision === 'string'
|
|
26
|
+
&& RUNTIME_GATE_DECISIONS.has(gate.decision);
|
|
27
|
+
}
|
|
28
|
+
function normalizeRuntimeResult(value) {
|
|
29
|
+
if (isValidRuntimeResult(value))
|
|
30
|
+
return value;
|
|
31
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
32
|
+
return null;
|
|
33
|
+
const slim = value;
|
|
34
|
+
if (slim.response_mode !== 'slim'
|
|
35
|
+
|| typeof slim.decision !== 'string'
|
|
36
|
+
|| !RUNTIME_GATE_DECISIONS.has(slim.decision)
|
|
37
|
+
|| typeof slim.risk_level !== 'string'
|
|
38
|
+
|| !RUNTIME_RISK_LEVELS.has(slim.risk_level)
|
|
39
|
+
|| typeof slim.gate_required !== 'boolean'
|
|
40
|
+
|| typeof slim.proof_required !== 'boolean'
|
|
41
|
+
|| typeof slim.proof_complete !== 'boolean') {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const allow = ['allow', 'proceed', 'warn'].includes(slim.decision);
|
|
45
|
+
const gateReceiptId = typeof slim.gate_receipt_id === 'string' && slim.gate_receipt_id
|
|
46
|
+
? slim.gate_receipt_id
|
|
47
|
+
: null;
|
|
48
|
+
return {
|
|
49
|
+
...slim,
|
|
50
|
+
ok: slim.ok !== false,
|
|
51
|
+
action: '',
|
|
52
|
+
agent_id: typeof slim.agent_id === 'string' ? slim.agent_id : null,
|
|
53
|
+
session_id: typeof slim.session_id === 'string' ? slim.session_id : null,
|
|
54
|
+
status: { health: slim.health || null, missed_hooks: slim.missed_hooks || [] },
|
|
55
|
+
decision_brief: {},
|
|
56
|
+
risk_gate: {
|
|
57
|
+
allow,
|
|
58
|
+
decision: slim.decision === 'proceed' ? 'allow' : slim.decision,
|
|
59
|
+
risk_level: slim.risk_level,
|
|
60
|
+
reasons: [],
|
|
61
|
+
gate_receipt_id: gateReceiptId,
|
|
62
|
+
},
|
|
63
|
+
relevant_lessons: [],
|
|
64
|
+
deployment_playbooks: [],
|
|
65
|
+
template_suggestion: {},
|
|
66
|
+
gate_receipt_id: gateReceiptId,
|
|
67
|
+
gate_receipt: gateReceiptId ? {
|
|
68
|
+
id: gateReceiptId,
|
|
69
|
+
required: slim.gate_required,
|
|
70
|
+
decision: slim.decision,
|
|
71
|
+
} : null,
|
|
72
|
+
proof_pack: {
|
|
73
|
+
required: slim.proof_required,
|
|
74
|
+
enforced: slim.proof_required,
|
|
75
|
+
fields: [],
|
|
76
|
+
missing: slim.proof_complete ? [] : ['required_proof'],
|
|
77
|
+
complete: slim.proof_complete,
|
|
78
|
+
commit_endpoint: '/v1/agent/commit',
|
|
79
|
+
rule: slim.proof_required ? 'proof_required_before_complete' : 'outcome_commit_required',
|
|
80
|
+
},
|
|
81
|
+
before_you_act: typeof slim.before_you_act === 'string' ? slim.before_you_act : null,
|
|
82
|
+
exact_next_action: typeof slim.exact_next_action === 'string' ? slim.exact_next_action : null,
|
|
83
|
+
auto_outcome_closure: null,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function highRiskRuntimeCanClose(runtime, proof, explicitReceiptId, now = Date.now()) {
|
|
87
|
+
const decision = String(runtime.risk_gate?.decision || '').toLowerCase();
|
|
88
|
+
const receiptDecision = String(runtime.gate_receipt?.decision || decision).toLowerCase();
|
|
89
|
+
const receiptId = typeof explicitReceiptId === 'string' && explicitReceiptId.trim()
|
|
90
|
+
? explicitReceiptId.trim()
|
|
91
|
+
: runtime.gate_receipt?.id || runtime.gate_receipt_id || '';
|
|
92
|
+
const receiptExpired = runtime.gate_receipt?.expires_at
|
|
93
|
+
? Date.parse(runtime.gate_receipt.expires_at) <= now
|
|
94
|
+
: false;
|
|
95
|
+
return Boolean(proof
|
|
96
|
+
&& Object.keys(proof).length > 0
|
|
97
|
+
&& runtime.risk_gate?.allow === true
|
|
98
|
+
&& ['allow', 'proceed', 'warn'].includes(decision)
|
|
99
|
+
&& ['allow', 'proceed', 'warn'].includes(receiptDecision)
|
|
100
|
+
&& runtime.proof_pack?.complete === true
|
|
101
|
+
&& receiptId
|
|
102
|
+
&& !receiptExpired
|
|
103
|
+
&& runtime.gate_receipt?.owner_approval_required !== true
|
|
104
|
+
&& runtime.intervention?.must_stop !== true
|
|
105
|
+
&& runtime.intervention?.allow !== false);
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=runtime-contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-contract.js","sourceRoot":"","sources":["../src/runtime-contract.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getmarrow/mcp",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.57",
|
|
4
4
|
"mcpName": "io.github.getmarrow/marrow",
|
|
5
5
|
"description": "Runtime control, proof, and fleet intelligence for MCP-compatible AI agents.",
|
|
6
6
|
"bin": {
|
|
7
|
-
"marrow-mcp": "dist/cli.js"
|
|
7
|
+
"marrow-mcp": "dist/cli.js",
|
|
8
|
+
"marrow-mcp-canary": "scripts/control-path-canary.cjs"
|
|
8
9
|
},
|
|
9
10
|
"main": "dist/index.js",
|
|
10
11
|
"types": "dist/index.d.ts",
|
|
@@ -13,7 +14,8 @@
|
|
|
13
14
|
"dev": "tsc --watch",
|
|
14
15
|
"test": "npm run build && node --test test/*.test.js",
|
|
15
16
|
"registry:check": "node scripts/mcp-registry-check.cjs",
|
|
16
|
-
"registry:verify": "node scripts/mcp-registry-verify.cjs"
|
|
17
|
+
"registry:verify": "node scripts/mcp-registry-verify.cjs",
|
|
18
|
+
"canary:control-path": "node scripts/control-path-canary.cjs"
|
|
17
19
|
},
|
|
18
20
|
"keywords": [
|
|
19
21
|
"mcp",
|
|
@@ -68,7 +70,8 @@
|
|
|
68
70
|
"README.md",
|
|
69
71
|
"server.json",
|
|
70
72
|
"scripts/mcp-registry-check.cjs",
|
|
71
|
-
"scripts/mcp-registry-verify.cjs"
|
|
73
|
+
"scripts/mcp-registry-verify.cjs",
|
|
74
|
+
"scripts/control-path-canary.cjs"
|
|
72
75
|
],
|
|
73
76
|
"dependencies": {
|
|
74
77
|
"@getmarrow/sdk": "^3.7.54"
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawnSync } = require('node:child_process');
|
|
3
|
+
const { resolve } = require('node:path');
|
|
4
|
+
const { version: packageVersion } = require('../package.json');
|
|
5
|
+
|
|
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
|
+
const cases = [
|
|
13
|
+
['marrow_status', {}],
|
|
14
|
+
['marrow_runtime_status', { fast: true }],
|
|
15
|
+
['marrow_orient', { autoWarn: true }],
|
|
16
|
+
['marrow_ask', { query: 'What should this agent verify before a safe release?' }],
|
|
17
|
+
['marrow_agent_runtime', { action: 'Review a local documentation note', type: 'general', surfaces: ['workspace'] }],
|
|
18
|
+
['marrow_first_value', { action: 'Verify Marrow control-path availability', type: 'review', surfaces: ['api'] }],
|
|
19
|
+
['marrow_buyer_proof', { periodDays: 7 }],
|
|
20
|
+
['marrow_governance_control_plane', {}],
|
|
21
|
+
['marrow_value_report', { period: '7d' }],
|
|
22
|
+
['marrow_fleet_lessons', { query: 'safe release verification', limit: 3 }],
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
function boundedMs(name, fallback, minimum = 100, maximum = 10_000) {
|
|
26
|
+
const parsed = Number(process.env[name] || fallback);
|
|
27
|
+
return Number.isFinite(parsed) ? Math.max(minimum, Math.min(maximum, Math.floor(parsed))) : fallback;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const transportTimeoutMs = boundedMs('MARROW_REQUEST_TIMEOUT_MS', 2_500, 250, 10_000);
|
|
31
|
+
const processTimeoutMs = transportTimeoutMs + 3_000;
|
|
32
|
+
const expectedVersion = process.env.MARROW_EXPECTED_MCP_VERSION || packageVersion;
|
|
33
|
+
const ceilings = {
|
|
34
|
+
marrow_status: boundedMs('MARROW_MCP_STATUS_CEILING_MS', 1_500),
|
|
35
|
+
marrow_ask: boundedMs('MARROW_MCP_ASK_CEILING_MS', 1_000),
|
|
36
|
+
marrow_agent_runtime: boundedMs('MARROW_MCP_RUNTIME_CEILING_MS', 750),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function toolPayload(message, name) {
|
|
40
|
+
if (!message) throw new Error(`${name} returned no MCP tool response`);
|
|
41
|
+
if (message.error) throw new Error(`${name} returned JSON-RPC error ${message.error.code}`);
|
|
42
|
+
const result = message.result || {};
|
|
43
|
+
const text = result.content?.[0]?.text;
|
|
44
|
+
if (typeof text !== 'string' || !text.trim()) throw new Error(`${name} returned no MCP tool payload`);
|
|
45
|
+
let payload;
|
|
46
|
+
try { payload = JSON.parse(text); } catch { throw new Error(`${name} returned invalid tool JSON`); }
|
|
47
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload) || Object.keys(payload).length === 0) {
|
|
48
|
+
throw new Error(`${name} returned an empty or invalid tool payload`);
|
|
49
|
+
}
|
|
50
|
+
if (result.isError || payload.ok === false || payload.available === false) {
|
|
51
|
+
const code = payload.error?.code || payload.error_code || 'tool_unavailable';
|
|
52
|
+
throw new Error(`${name} unavailable (${code})`);
|
|
53
|
+
}
|
|
54
|
+
return payload;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function validatePayload(name, payload) {
|
|
58
|
+
const requireField = (valid, field) => {
|
|
59
|
+
if (!valid) throw new Error(`${name} returned an invalid ${field} contract`);
|
|
60
|
+
};
|
|
61
|
+
if (name === 'marrow_status' || name === 'marrow_runtime_status') {
|
|
62
|
+
requireField(
|
|
63
|
+
typeof payload.status === 'string'
|
|
64
|
+
|| typeof payload.health === 'string'
|
|
65
|
+
|| typeof payload.active === 'boolean'
|
|
66
|
+
|| (payload.runtime && typeof payload.runtime === 'object'),
|
|
67
|
+
'status',
|
|
68
|
+
);
|
|
69
|
+
} else if (name === 'marrow_orient') {
|
|
70
|
+
requireField(Array.isArray(payload.warnings) && typeof payload.shouldPause === 'boolean', 'orientation');
|
|
71
|
+
} else if (name === 'marrow_ask') {
|
|
72
|
+
requireField(typeof payload.answer === 'string' && payload.answer.trim().length > 0, 'answer');
|
|
73
|
+
} else if (name === 'marrow_agent_runtime') {
|
|
74
|
+
requireField(
|
|
75
|
+
payload.risk_gate
|
|
76
|
+
&& typeof payload.risk_gate === 'object'
|
|
77
|
+
&& typeof payload.risk_gate.allow === 'boolean'
|
|
78
|
+
&& typeof payload.risk_gate.decision === 'string'
|
|
79
|
+
&& payload.proof_pack
|
|
80
|
+
&& typeof payload.proof_pack.complete === 'boolean',
|
|
81
|
+
'runtime gate',
|
|
82
|
+
);
|
|
83
|
+
} else if (name === 'marrow_first_value') {
|
|
84
|
+
requireField(typeof payload.active === 'boolean' && typeof payload.headline === 'string', 'first-value');
|
|
85
|
+
} else if (name === 'marrow_value_report') {
|
|
86
|
+
requireField(
|
|
87
|
+
payload.period
|
|
88
|
+
&& typeof payload.period.days === 'number'
|
|
89
|
+
&& payload.metrics
|
|
90
|
+
&& typeof payload.metrics.decisions?.total === 'number'
|
|
91
|
+
&& payload.fleet
|
|
92
|
+
&& typeof payload.fleet.active_agents === 'number',
|
|
93
|
+
'value report',
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function runCase(name, args) {
|
|
99
|
+
const input = [
|
|
100
|
+
{ jsonrpc: '2.0', id: 1, method: 'initialize', params: {} },
|
|
101
|
+
{ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} },
|
|
102
|
+
{ jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name, arguments: args } },
|
|
103
|
+
].map((row) => JSON.stringify(row)).join('\n') + '\n';
|
|
104
|
+
const started = Date.now();
|
|
105
|
+
const child = spawnSync(process.execPath, [resolve(__dirname, '../dist/cli.js')], {
|
|
106
|
+
env: {
|
|
107
|
+
...process.env,
|
|
108
|
+
MARROW_API_KEY: key,
|
|
109
|
+
MARROW_BASE_URL: process.env.MARROW_BASE_URL || 'https://api.getmarrow.ai',
|
|
110
|
+
MARROW_AUTO_ENROLL: 'false',
|
|
111
|
+
MARROW_TOOL_PROFILE: 'full',
|
|
112
|
+
MARROW_CONTROL_PATH_CANARY: '1',
|
|
113
|
+
MARROW_REQUEST_TIMEOUT_MS: String(transportTimeoutMs),
|
|
114
|
+
},
|
|
115
|
+
input,
|
|
116
|
+
encoding: 'utf8',
|
|
117
|
+
timeout: processTimeoutMs,
|
|
118
|
+
maxBuffer: 256 * 1024,
|
|
119
|
+
});
|
|
120
|
+
const latencyMs = Date.now() - started;
|
|
121
|
+
if (child.error) {
|
|
122
|
+
const code = child.error.code === 'ETIMEDOUT' ? 'process_timeout' : 'process_error';
|
|
123
|
+
throw new Error(`${name} ${code}`);
|
|
124
|
+
}
|
|
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
|
+
|
|
143
|
+
try {
|
|
144
|
+
const results = cases.map(([name, args]) => runCase(name, args));
|
|
145
|
+
const versions = [...new Set(results.map((row) => row.package_version))];
|
|
146
|
+
if (versions.length !== 1) throw new Error('MCP canary processes reported inconsistent package versions');
|
|
147
|
+
if (versions[0] !== expectedVersion) {
|
|
148
|
+
throw new Error(`MCP canary loaded ${versions[0]} instead of expected ${expectedVersion}`);
|
|
149
|
+
}
|
|
150
|
+
const core = results.filter((row) => Object.hasOwn(ceilings, row.tool));
|
|
151
|
+
const slow = core.find((row) => row.latency_ms > ceilings[row.tool]);
|
|
152
|
+
if (slow && process.env.MARROW_MCP_CANARY_ENFORCE_LATENCY !== '0') {
|
|
153
|
+
throw new Error(`${slow.tool} exceeded its ${ceilings[slow.tool]}ms release ceiling`);
|
|
154
|
+
}
|
|
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
|
+
}
|
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.57",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@getmarrow/mcp",
|
|
14
|
-
"version": "3.9.
|
|
14
|
+
"version": "3.9.57",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|