@getmarrow/mcp 3.9.55 → 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.
@@ -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.55",
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.55",
9
+ "version": "3.9.57",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@getmarrow/mcp",
14
- "version": "3.9.55",
14
+ "version": "3.9.57",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },