mcp-lint 0.3.0 → 0.4.0
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 +110 -0
- package/dist/cli/commands/preflight.d.ts +2 -0
- package/dist/cli/commands/preflight.js +109 -0
- package/dist/cli/commands/preflight.js.map +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/preflight/engine.d.ts +2 -0
- package/dist/preflight/engine.js +73 -0
- package/dist/preflight/engine.js.map +1 -0
- package/dist/preflight/index.d.ts +4 -0
- package/dist/preflight/index.js +4 -0
- package/dist/preflight/index.js.map +1 -0
- package/dist/preflight/policy.d.ts +4 -0
- package/dist/preflight/policy.js +97 -0
- package/dist/preflight/policy.js.map +1 -0
- package/dist/preflight/scorer.d.ts +7 -0
- package/dist/preflight/scorer.js +121 -0
- package/dist/preflight/scorer.js.map +1 -0
- package/dist/preflight/types.d.ts +57 -0
- package/dist/preflight/types.js +2 -0
- package/dist/preflight/types.js.map +1 -0
- package/examples/actions/destructive-delete.json +11 -0
- package/examples/actions/rewrite-tmp.json +8 -0
- package/examples/actions/safe-read.json +8 -0
- package/examples/github-action.yml +23 -0
- package/examples/policies/default.yml +59 -0
- package/examples/policies/permissive-dev.yml +33 -0
- package/examples/policies/strict.yml +42 -0
- package/package.json +23 -4
package/README.md
CHANGED
|
@@ -440,6 +440,116 @@ const fixed = applyFixes(tools, diagnostics);
|
|
|
440
440
|
|
|
441
441
|
---
|
|
442
442
|
|
|
443
|
+
## `mcp-lint preflight` — Runtime Decision Firewall
|
|
444
|
+
|
|
445
|
+
**Lint catches bad schemas. Preflight catches bad actions.**
|
|
446
|
+
|
|
447
|
+
At runtime, your agent is about to call a tool — delete a file, send a request, charge a card. Preflight sits between the agent and the action and answers:
|
|
448
|
+
|
|
449
|
+
- Is it allowed?
|
|
450
|
+
- How risky is it? (0.0–1.0 deterministic score)
|
|
451
|
+
- Is it reversible?
|
|
452
|
+
- Does it need human approval?
|
|
453
|
+
- Is there a safer alternative?
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
$ mcp-lint preflight action.json --policy preflight.yml
|
|
457
|
+
|
|
458
|
+
Preflight Decision
|
|
459
|
+
────────────────────────────────────────
|
|
460
|
+
Decision: DENY
|
|
461
|
+
Risk score: 1.00
|
|
462
|
+
Reversibility: irreversible
|
|
463
|
+
Policies: block-destructive-prod
|
|
464
|
+
Reasons: base_shell, destructive_pattern, sensitive_target, irreversible_operation
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Action format
|
|
468
|
+
|
|
469
|
+
```json
|
|
470
|
+
{
|
|
471
|
+
"tool_type": "shell",
|
|
472
|
+
"tool_name": "bash",
|
|
473
|
+
"action": "rm -rf /prod/data",
|
|
474
|
+
"target": "/prod/data",
|
|
475
|
+
"context": { "environment": "prod" }
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Policy file (YAML)
|
|
480
|
+
|
|
481
|
+
```yaml
|
|
482
|
+
version: 1
|
|
483
|
+
defaults:
|
|
484
|
+
approval_threshold: 0.70
|
|
485
|
+
block_threshold: 0.92
|
|
486
|
+
|
|
487
|
+
rules:
|
|
488
|
+
- id: block-prod-delete
|
|
489
|
+
when:
|
|
490
|
+
tool_type: shell
|
|
491
|
+
action_matches: ["rm -rf"]
|
|
492
|
+
target_matches: ["/prod", "/etc"]
|
|
493
|
+
effect: deny
|
|
494
|
+
|
|
495
|
+
- id: rewrite-tmp-delete
|
|
496
|
+
when:
|
|
497
|
+
tool_type: shell
|
|
498
|
+
action_matches: ["rm -rf ./tmp"]
|
|
499
|
+
effect: allow_with_rewrite
|
|
500
|
+
rewrite:
|
|
501
|
+
tool_type: file_delete
|
|
502
|
+
action: move_to_trash
|
|
503
|
+
target: ./tmp
|
|
504
|
+
|
|
505
|
+
- id: approval-payments
|
|
506
|
+
when:
|
|
507
|
+
tool_type: payment
|
|
508
|
+
effect: require_approval
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
Sample policies in `examples/policies/`: `default.yml`, `strict.yml`, `permissive-dev.yml`.
|
|
512
|
+
|
|
513
|
+
### SDK — embed in your MCP server or agent runtime
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
import { preflight, loadPolicy } from 'mcp-lint/preflight';
|
|
517
|
+
|
|
518
|
+
const policy = await loadPolicy('./preflight.yml');
|
|
519
|
+
|
|
520
|
+
const decision = preflight(
|
|
521
|
+
{
|
|
522
|
+
tool_type: 'shell',
|
|
523
|
+
tool_name: 'bash',
|
|
524
|
+
action: 'rm -rf /tmp',
|
|
525
|
+
target: '/tmp',
|
|
526
|
+
},
|
|
527
|
+
policy,
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
if (decision.decision === 'deny') {
|
|
531
|
+
throw new Error(`Blocked: ${decision.reasons.join(', ')}`);
|
|
532
|
+
}
|
|
533
|
+
if (decision.decision === 'require_approval') {
|
|
534
|
+
await askUser(decision);
|
|
535
|
+
}
|
|
536
|
+
if (decision.decision === 'allow_with_rewrite') {
|
|
537
|
+
return executeSafer(decision.safe_alternative);
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### Exit codes
|
|
542
|
+
|
|
543
|
+
| Code | Meaning |
|
|
544
|
+
|------|---------|
|
|
545
|
+
| `0` | allow / allow_with_rewrite |
|
|
546
|
+
| `1` | require_approval |
|
|
547
|
+
| `2` | deny / error |
|
|
548
|
+
|
|
549
|
+
Use them directly in shell wrappers or CI gates.
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
443
553
|
## License
|
|
444
554
|
|
|
445
555
|
MIT © Robert
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { preflight } from '../../preflight/engine.js';
|
|
4
|
+
import { loadPolicy } from '../../preflight/policy.js';
|
|
5
|
+
export function registerPreflightCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('preflight [action-file]')
|
|
8
|
+
.description('Evaluate a proposed agent action against a policy before execution')
|
|
9
|
+
.option('--policy <path>', 'Path to preflight policy YAML file')
|
|
10
|
+
.option('--format <format>', 'Output format: terminal|json', 'terminal')
|
|
11
|
+
.option('--no-color', 'Disable terminal colors')
|
|
12
|
+
.option('--stdin', 'Read action JSON from stdin')
|
|
13
|
+
.action(async (actionFile, options) => {
|
|
14
|
+
if (!options.color)
|
|
15
|
+
chalk.level = 0;
|
|
16
|
+
try {
|
|
17
|
+
const action = await readAction(actionFile, options.stdin ?? false);
|
|
18
|
+
const policy = options.policy ? await loadPolicy(options.policy) : undefined;
|
|
19
|
+
const decision = preflight(action, policy);
|
|
20
|
+
if (options.format === 'json') {
|
|
21
|
+
process.stdout.write(JSON.stringify(decision, null, 2) + '\n');
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
process.stdout.write(renderTerminal(decision));
|
|
25
|
+
}
|
|
26
|
+
const exitCode = decisionExitCode(decision);
|
|
27
|
+
process.exit(exitCode);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
31
|
+
process.exit(2);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async function readAction(path, stdin) {
|
|
36
|
+
let raw;
|
|
37
|
+
if (stdin || !path) {
|
|
38
|
+
if (process.stdin.isTTY && !stdin) {
|
|
39
|
+
throw new Error('Specify an action file or use --stdin');
|
|
40
|
+
}
|
|
41
|
+
raw = await readStdin();
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
raw = await readFile(path, 'utf8');
|
|
45
|
+
}
|
|
46
|
+
return JSON.parse(raw);
|
|
47
|
+
}
|
|
48
|
+
async function readStdin() {
|
|
49
|
+
const chunks = [];
|
|
50
|
+
for await (const chunk of process.stdin) {
|
|
51
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
52
|
+
}
|
|
53
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
54
|
+
}
|
|
55
|
+
function decisionExitCode(d) {
|
|
56
|
+
switch (d.decision) {
|
|
57
|
+
case 'allow':
|
|
58
|
+
case 'allow_with_rewrite':
|
|
59
|
+
return 0;
|
|
60
|
+
case 'require_approval':
|
|
61
|
+
return 1;
|
|
62
|
+
case 'deny':
|
|
63
|
+
return 2;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function renderTerminal(d) {
|
|
67
|
+
const lines = [];
|
|
68
|
+
const decisionColor = d.decision === 'deny'
|
|
69
|
+
? chalk.red
|
|
70
|
+
: d.decision === 'require_approval'
|
|
71
|
+
? chalk.yellow
|
|
72
|
+
: d.decision === 'allow_with_rewrite'
|
|
73
|
+
? chalk.cyan
|
|
74
|
+
: chalk.green;
|
|
75
|
+
lines.push('');
|
|
76
|
+
lines.push(chalk.bold('Preflight Decision'));
|
|
77
|
+
lines.push('─'.repeat(40));
|
|
78
|
+
lines.push(`Decision: ${decisionColor(d.decision.toUpperCase())}`);
|
|
79
|
+
lines.push(`Risk score: ${riskColor(d.risk_score)}`);
|
|
80
|
+
lines.push(`Reversibility: ${d.reversibility}`);
|
|
81
|
+
lines.push(`Est. cost: $${d.estimated_cost_usd.toFixed(2)}`);
|
|
82
|
+
if (d.matched_policies.length > 0) {
|
|
83
|
+
lines.push(`Policies: ${d.matched_policies.join(', ')}`);
|
|
84
|
+
}
|
|
85
|
+
if (d.reasons.length > 0) {
|
|
86
|
+
lines.push(`Reasons: ${d.reasons.join(', ')}`);
|
|
87
|
+
}
|
|
88
|
+
if (d.safe_alternative) {
|
|
89
|
+
lines.push('');
|
|
90
|
+
lines.push(chalk.bold('Safe alternative:'));
|
|
91
|
+
lines.push(` ${d.safe_alternative.tool_type} → ${d.safe_alternative.action}`);
|
|
92
|
+
if (d.safe_alternative.target) {
|
|
93
|
+
lines.push(` target: ${d.safe_alternative.target}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
lines.push('');
|
|
97
|
+
return lines.join('\n');
|
|
98
|
+
}
|
|
99
|
+
function riskColor(score) {
|
|
100
|
+
const s = score.toFixed(2);
|
|
101
|
+
if (score >= 0.9)
|
|
102
|
+
return chalk.red(s);
|
|
103
|
+
if (score >= 0.7)
|
|
104
|
+
return chalk.yellow(s);
|
|
105
|
+
if (score >= 0.4)
|
|
106
|
+
return chalk.cyan(s);
|
|
107
|
+
return chalk.green(s);
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=preflight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.js","sourceRoot":"","sources":["../../../src/cli/commands/preflight.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -6,16 +6,18 @@ import { registerInitCommand } from './cli/commands/init.js';
|
|
|
6
6
|
import { registerCompatCommand } from './cli/commands/compat.js';
|
|
7
7
|
import { registerExplainCommand } from './cli/commands/explain.js';
|
|
8
8
|
import { registerDiffCommand } from './cli/commands/diff.js';
|
|
9
|
+
import { registerPreflightCommand } from './cli/commands/preflight.js';
|
|
9
10
|
const program = new Command();
|
|
10
11
|
program
|
|
11
12
|
.name('mcp-lint')
|
|
12
13
|
.description('Lint MCP server tool schemas for cross-client compatibility')
|
|
13
|
-
.version('0.
|
|
14
|
+
.version('0.4.0');
|
|
14
15
|
registerCheckCommand(program);
|
|
15
16
|
registerFixCommand(program);
|
|
16
17
|
registerInitCommand(program);
|
|
17
18
|
registerCompatCommand(program);
|
|
18
19
|
registerExplainCommand(program);
|
|
19
20
|
registerDiffCommand(program);
|
|
21
|
+
registerPreflightCommand(program);
|
|
20
22
|
program.parse();
|
|
21
23
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAEvE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,UAAU,CAAC;KAChB,WAAW,CAAC,6DAA6D,CAAC;KAC1E,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAC5B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,sBAAsB,CAAC,OAAO,CAAC,CAAC;AAChC,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,wBAAwB,CAAC,OAAO,CAAC,CAAC;AAElC,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { matchRule } from './policy.js';
|
|
2
|
+
import { scoreAction } from './scorer.js';
|
|
3
|
+
const DEFAULT_APPROVAL_THRESHOLD = 0.7;
|
|
4
|
+
const DEFAULT_BLOCK_THRESHOLD = 0.92;
|
|
5
|
+
export function preflight(action, policy) {
|
|
6
|
+
validateAction(action);
|
|
7
|
+
const { score, reasons, reversibility } = scoreAction(action);
|
|
8
|
+
const approvalThreshold = policy?.defaults?.approval_threshold ?? DEFAULT_APPROVAL_THRESHOLD;
|
|
9
|
+
const blockThreshold = policy?.defaults?.block_threshold ?? DEFAULT_BLOCK_THRESHOLD;
|
|
10
|
+
const matched = [];
|
|
11
|
+
if (policy) {
|
|
12
|
+
for (const rule of policy.rules) {
|
|
13
|
+
if (matchRule(rule, action, score)) {
|
|
14
|
+
matched.push(rule);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
let decision = null;
|
|
19
|
+
let safeAlternative = undefined;
|
|
20
|
+
const policyReasons = [];
|
|
21
|
+
for (const rule of matched) {
|
|
22
|
+
if (rule.reason)
|
|
23
|
+
policyReasons.push(rule.reason);
|
|
24
|
+
if (rule.effect === 'deny') {
|
|
25
|
+
decision = 'deny';
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
if (rule.effect === 'require_approval') {
|
|
29
|
+
decision = 'require_approval';
|
|
30
|
+
}
|
|
31
|
+
else if (rule.effect === 'allow_with_rewrite' && decision === null) {
|
|
32
|
+
decision = 'allow_with_rewrite';
|
|
33
|
+
safeAlternative = rule.rewrite;
|
|
34
|
+
}
|
|
35
|
+
else if (rule.effect === 'allow' && decision === null) {
|
|
36
|
+
decision = 'allow';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (decision === null) {
|
|
40
|
+
if (score >= blockThreshold) {
|
|
41
|
+
decision = 'deny';
|
|
42
|
+
policyReasons.push('risk_above_block_threshold');
|
|
43
|
+
}
|
|
44
|
+
else if (score >= approvalThreshold) {
|
|
45
|
+
decision = 'require_approval';
|
|
46
|
+
policyReasons.push('risk_above_approval_threshold');
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
decision = 'allow';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
decision,
|
|
54
|
+
risk_score: Number(score.toFixed(2)),
|
|
55
|
+
reversibility,
|
|
56
|
+
estimated_cost_usd: action.estimated_cost_usd ?? 0,
|
|
57
|
+
matched_policies: matched.map((r) => r.id),
|
|
58
|
+
reasons: [...reasons, ...policyReasons],
|
|
59
|
+
safe_alternative: safeAlternative,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function validateAction(action) {
|
|
63
|
+
if (!action || typeof action !== 'object') {
|
|
64
|
+
throw new Error('Action must be an object');
|
|
65
|
+
}
|
|
66
|
+
if (!action.tool_type)
|
|
67
|
+
throw new Error('Action missing tool_type');
|
|
68
|
+
if (!action.tool_name)
|
|
69
|
+
throw new Error('Action missing tool_name');
|
|
70
|
+
if (typeof action.action !== 'string')
|
|
71
|
+
throw new Error('Action missing action string');
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/preflight/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { preflight } from './engine.js';
|
|
2
|
+
export { loadPolicy, parsePolicy, matchRule } from './policy.js';
|
|
3
|
+
export { scoreAction } from './scorer.js';
|
|
4
|
+
export type { Action, Decision, DecisionType, Effect, Policy, PolicyRule, Rewrite, Reversibility, ToolType, } from './types.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/preflight/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Action, Policy, PolicyRule } from './types.js';
|
|
2
|
+
export declare function loadPolicy(path: string): Promise<Policy>;
|
|
3
|
+
export declare function parsePolicy(yaml: string): Policy;
|
|
4
|
+
export declare function matchRule(rule: PolicyRule, action: Action, riskScore: number): boolean;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import YAML from 'yaml';
|
|
3
|
+
export async function loadPolicy(path) {
|
|
4
|
+
const raw = await readFile(path, 'utf8');
|
|
5
|
+
const parsed = YAML.parse(raw);
|
|
6
|
+
return normalizePolicy(parsed);
|
|
7
|
+
}
|
|
8
|
+
export function parsePolicy(yaml) {
|
|
9
|
+
const parsed = YAML.parse(yaml);
|
|
10
|
+
return normalizePolicy(parsed);
|
|
11
|
+
}
|
|
12
|
+
function normalizePolicy(parsed) {
|
|
13
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
14
|
+
throw new Error('Policy file is empty or not an object');
|
|
15
|
+
}
|
|
16
|
+
const rules = Array.isArray(parsed.rules) ? parsed.rules : [];
|
|
17
|
+
for (const [idx, rule] of rules.entries()) {
|
|
18
|
+
if (!rule.id)
|
|
19
|
+
throw new Error(`Policy rule #${idx} missing "id"`);
|
|
20
|
+
if (!rule.when)
|
|
21
|
+
throw new Error(`Policy rule "${rule.id}" missing "when"`);
|
|
22
|
+
if (!rule.effect)
|
|
23
|
+
throw new Error(`Policy rule "${rule.id}" missing "effect"`);
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
version: parsed.version ?? 1,
|
|
27
|
+
defaults: parsed.defaults ?? {},
|
|
28
|
+
budgets: parsed.budgets ?? {},
|
|
29
|
+
rules,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function matchRule(rule, action, riskScore) {
|
|
33
|
+
const w = rule.when;
|
|
34
|
+
if (w.tool_type) {
|
|
35
|
+
const types = Array.isArray(w.tool_type) ? w.tool_type : [w.tool_type];
|
|
36
|
+
if (!types.includes(action.tool_type))
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (w.tool_name) {
|
|
40
|
+
const names = Array.isArray(w.tool_name) ? w.tool_name : [w.tool_name];
|
|
41
|
+
if (!names.includes(action.tool_name))
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (w.action_matches && w.action_matches.length > 0) {
|
|
45
|
+
const hit = w.action_matches.some((m) => action.action.includes(m));
|
|
46
|
+
if (!hit)
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (w.target_matches && w.target_matches.length > 0) {
|
|
50
|
+
const target = action.target ?? '';
|
|
51
|
+
const hit = w.target_matches.some((m) => matchesGlob(target, m));
|
|
52
|
+
if (!hit)
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
if (w.domain_not_in_allowlist) {
|
|
56
|
+
const domain = extractDomain(action);
|
|
57
|
+
const allow = w.allowlist ?? [];
|
|
58
|
+
if (!domain)
|
|
59
|
+
return false;
|
|
60
|
+
if (allow.includes(domain))
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (typeof w.min_risk === 'number') {
|
|
64
|
+
if (riskScore < w.min_risk)
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (typeof w.max_cost_usd === 'number') {
|
|
68
|
+
const cost = action.estimated_cost_usd ?? 0;
|
|
69
|
+
if (cost <= w.max_cost_usd)
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
function matchesGlob(value, pattern) {
|
|
75
|
+
if (pattern.includes('**') || pattern.includes('*')) {
|
|
76
|
+
const regex = new RegExp('^' +
|
|
77
|
+
pattern
|
|
78
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
|
|
79
|
+
.replace(/\*\*/g, '§§')
|
|
80
|
+
.replace(/\*/g, '[^/]*')
|
|
81
|
+
.replace(/§§/g, '.*') +
|
|
82
|
+
'$');
|
|
83
|
+
return regex.test(value);
|
|
84
|
+
}
|
|
85
|
+
return value.includes(pattern);
|
|
86
|
+
}
|
|
87
|
+
function extractDomain(action) {
|
|
88
|
+
const candidate = action.target ?? action.action;
|
|
89
|
+
const match = candidate.match(/https?:\/\/([^/\s]+)/i);
|
|
90
|
+
if (match)
|
|
91
|
+
return match[1].toLowerCase();
|
|
92
|
+
if (action.context && typeof action.context.domain === 'string') {
|
|
93
|
+
return action.context.domain.toLowerCase();
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/preflight/policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
const BASE_SCORES = {
|
|
2
|
+
file_read: 0.1,
|
|
3
|
+
file_write: 0.35,
|
|
4
|
+
file_delete: 0.75,
|
|
5
|
+
shell: 0.5,
|
|
6
|
+
http: 0.35,
|
|
7
|
+
mcp_tool: 0.3,
|
|
8
|
+
email: 0.6,
|
|
9
|
+
payment: 0.95,
|
|
10
|
+
database: 0.7,
|
|
11
|
+
other: 0.4,
|
|
12
|
+
};
|
|
13
|
+
const DESTRUCTIVE_PATTERNS = [
|
|
14
|
+
/\brm\s+-rf\b/i,
|
|
15
|
+
/\brmdir\s+\/s\b/i,
|
|
16
|
+
/\bdel\s+\/s\b/i,
|
|
17
|
+
/\bdrop\s+table\b/i,
|
|
18
|
+
/\btruncate\s+table\b/i,
|
|
19
|
+
/\bformat\s+\/\w+/i,
|
|
20
|
+
/\bmkfs\b/i,
|
|
21
|
+
/\b:\(\)\{\s*:\|:&\s*\};:/,
|
|
22
|
+
/\bgit\s+push\s+.*--force\b/i,
|
|
23
|
+
/\bgit\s+reset\s+--hard\b/i,
|
|
24
|
+
];
|
|
25
|
+
const SENSITIVE_TARGETS = [
|
|
26
|
+
/\/etc\b/,
|
|
27
|
+
/\/prod\b/,
|
|
28
|
+
/\/production\b/,
|
|
29
|
+
/\/app\/data\b/,
|
|
30
|
+
/\.env(\.|$)/,
|
|
31
|
+
/\bsecrets?\b/i,
|
|
32
|
+
/\bcredentials?\b/i,
|
|
33
|
+
/\/root\b/,
|
|
34
|
+
/\bC:\\Windows\b/i,
|
|
35
|
+
];
|
|
36
|
+
const MASS_OPERATION_PATTERNS = [
|
|
37
|
+
/\*\*\/\*/,
|
|
38
|
+
/\brecursive\b/i,
|
|
39
|
+
/\s+-r\b/,
|
|
40
|
+
/\s+-R\b/,
|
|
41
|
+
/\bALL\b/,
|
|
42
|
+
];
|
|
43
|
+
export function scoreAction(action) {
|
|
44
|
+
const reasons = [];
|
|
45
|
+
let score = BASE_SCORES[action.tool_type] ?? 0.4;
|
|
46
|
+
reasons.push(`base_${action.tool_type}`);
|
|
47
|
+
const actionText = action.action ?? '';
|
|
48
|
+
const targetText = action.target ?? '';
|
|
49
|
+
const haystack = `${actionText} ${targetText}`;
|
|
50
|
+
let destructive = false;
|
|
51
|
+
for (const pattern of DESTRUCTIVE_PATTERNS) {
|
|
52
|
+
if (pattern.test(haystack)) {
|
|
53
|
+
destructive = true;
|
|
54
|
+
score += 0.25;
|
|
55
|
+
reasons.push('destructive_pattern');
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (action.tool_type === 'file_delete') {
|
|
60
|
+
destructive = true;
|
|
61
|
+
}
|
|
62
|
+
for (const pattern of SENSITIVE_TARGETS) {
|
|
63
|
+
if (pattern.test(haystack)) {
|
|
64
|
+
score += 0.2;
|
|
65
|
+
reasons.push('sensitive_target');
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const pattern of MASS_OPERATION_PATTERNS) {
|
|
70
|
+
if (pattern.test(haystack)) {
|
|
71
|
+
score += 0.1;
|
|
72
|
+
reasons.push('mass_operation');
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (action.context?.environment === 'prod' || action.context?.environment === 'production') {
|
|
77
|
+
score += 0.15;
|
|
78
|
+
reasons.push('prod_environment');
|
|
79
|
+
}
|
|
80
|
+
const cost = action.estimated_cost_usd ?? 0;
|
|
81
|
+
if (cost >= 10) {
|
|
82
|
+
score += 0.15;
|
|
83
|
+
reasons.push('high_cost');
|
|
84
|
+
}
|
|
85
|
+
else if (cost >= 1) {
|
|
86
|
+
score += 0.05;
|
|
87
|
+
reasons.push('nontrivial_cost');
|
|
88
|
+
}
|
|
89
|
+
score = Math.min(1, Math.max(0, score));
|
|
90
|
+
const reversibility = computeReversibility(action, destructive);
|
|
91
|
+
if (reversibility === 'irreversible') {
|
|
92
|
+
reasons.push('irreversible_operation');
|
|
93
|
+
}
|
|
94
|
+
return { score, reasons, reversibility };
|
|
95
|
+
}
|
|
96
|
+
function computeReversibility(action, destructive) {
|
|
97
|
+
if (destructive)
|
|
98
|
+
return 'irreversible';
|
|
99
|
+
switch (action.tool_type) {
|
|
100
|
+
case 'file_read':
|
|
101
|
+
return 'reversible';
|
|
102
|
+
case 'file_write':
|
|
103
|
+
return 'partially_reversible';
|
|
104
|
+
case 'file_delete':
|
|
105
|
+
return 'irreversible';
|
|
106
|
+
case 'payment':
|
|
107
|
+
case 'email':
|
|
108
|
+
return 'irreversible';
|
|
109
|
+
case 'http':
|
|
110
|
+
return 'partially_reversible';
|
|
111
|
+
case 'database':
|
|
112
|
+
return 'partially_reversible';
|
|
113
|
+
case 'shell':
|
|
114
|
+
return 'partially_reversible';
|
|
115
|
+
case 'mcp_tool':
|
|
116
|
+
return 'partially_reversible';
|
|
117
|
+
default:
|
|
118
|
+
return 'partially_reversible';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=scorer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scorer.js","sourceRoot":"","sources":["../../src/preflight/scorer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type ToolType = 'shell' | 'file_read' | 'file_write' | 'file_delete' | 'http' | 'mcp_tool' | 'email' | 'payment' | 'database' | 'other';
|
|
2
|
+
export type Reversibility = 'reversible' | 'partially_reversible' | 'irreversible';
|
|
3
|
+
export type DecisionType = 'allow' | 'deny' | 'require_approval' | 'allow_with_rewrite';
|
|
4
|
+
export type Effect = 'allow' | 'deny' | 'require_approval' | 'allow_with_rewrite';
|
|
5
|
+
export interface Action {
|
|
6
|
+
actor_id?: string;
|
|
7
|
+
workspace_id?: string;
|
|
8
|
+
tool_type: ToolType;
|
|
9
|
+
tool_name: string;
|
|
10
|
+
action: string;
|
|
11
|
+
target?: string;
|
|
12
|
+
context?: Record<string, unknown>;
|
|
13
|
+
estimated_cost_usd?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface Rewrite {
|
|
16
|
+
tool_type: ToolType;
|
|
17
|
+
tool_name?: string;
|
|
18
|
+
action: string;
|
|
19
|
+
target?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PolicyRule {
|
|
22
|
+
id: string;
|
|
23
|
+
when: {
|
|
24
|
+
tool_type?: ToolType | ToolType[];
|
|
25
|
+
tool_name?: string | string[];
|
|
26
|
+
action_matches?: string[];
|
|
27
|
+
target_matches?: string[];
|
|
28
|
+
domain_not_in_allowlist?: boolean;
|
|
29
|
+
allowlist?: string[];
|
|
30
|
+
min_risk?: number;
|
|
31
|
+
max_cost_usd?: number;
|
|
32
|
+
};
|
|
33
|
+
effect: Effect;
|
|
34
|
+
rewrite?: Rewrite;
|
|
35
|
+
reason?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface Policy {
|
|
38
|
+
version: number;
|
|
39
|
+
defaults?: {
|
|
40
|
+
approval_threshold?: number;
|
|
41
|
+
block_threshold?: number;
|
|
42
|
+
};
|
|
43
|
+
budgets?: {
|
|
44
|
+
max_http_cost_usd_per_action?: number;
|
|
45
|
+
max_model_cost_usd_per_action?: number;
|
|
46
|
+
};
|
|
47
|
+
rules: PolicyRule[];
|
|
48
|
+
}
|
|
49
|
+
export interface Decision {
|
|
50
|
+
decision: DecisionType;
|
|
51
|
+
risk_score: number;
|
|
52
|
+
reversibility: Reversibility;
|
|
53
|
+
estimated_cost_usd: number;
|
|
54
|
+
matched_policies: string[];
|
|
55
|
+
reasons: string[];
|
|
56
|
+
safe_alternative?: Rewrite;
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/preflight/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: MCP Schema Lint (example)
|
|
2
|
+
|
|
3
|
+
# Example workflow showing how to use mcp-lint in CI.
|
|
4
|
+
# Copy this to your own repo and adjust the `input` path.
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
pull_request:
|
|
8
|
+
push:
|
|
9
|
+
branches: [main]
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
lint:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Lint MCP tool schemas
|
|
18
|
+
uses: robert19001-cmyk/mcp-lint@v0.3.0
|
|
19
|
+
with:
|
|
20
|
+
input: tools.json
|
|
21
|
+
severity: warning
|
|
22
|
+
score: 'true'
|
|
23
|
+
fail_on: error
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
|
|
3
|
+
defaults:
|
|
4
|
+
approval_threshold: 0.70
|
|
5
|
+
block_threshold: 0.92
|
|
6
|
+
|
|
7
|
+
budgets:
|
|
8
|
+
max_http_cost_usd_per_action: 2
|
|
9
|
+
max_model_cost_usd_per_action: 1
|
|
10
|
+
|
|
11
|
+
rules:
|
|
12
|
+
- id: block-destructive-prod
|
|
13
|
+
when:
|
|
14
|
+
tool_type: shell
|
|
15
|
+
action_matches: ["rm -rf", "rmdir /s", "del /s"]
|
|
16
|
+
target_matches: ["/prod", "/etc", "/app/data", "C:\\Windows"]
|
|
17
|
+
effect: deny
|
|
18
|
+
reason: Destructive operations on production paths are forbidden
|
|
19
|
+
|
|
20
|
+
- id: approval-sensitive-write
|
|
21
|
+
when:
|
|
22
|
+
tool_type: file_write
|
|
23
|
+
target_matches: ["**/.env*", "**/secrets*", "**/credentials*"]
|
|
24
|
+
effect: require_approval
|
|
25
|
+
reason: Writing to sensitive files requires approval
|
|
26
|
+
|
|
27
|
+
- id: rewrite-tmp-delete
|
|
28
|
+
when:
|
|
29
|
+
tool_type: shell
|
|
30
|
+
action_matches: ["rm -rf ./tmp", "rm -rf ./cache", "rm -rf ./build"]
|
|
31
|
+
effect: allow_with_rewrite
|
|
32
|
+
rewrite:
|
|
33
|
+
tool_type: file_delete
|
|
34
|
+
action: move_to_trash
|
|
35
|
+
target: ./tmp
|
|
36
|
+
reason: Prefer moving to trash over hard delete
|
|
37
|
+
|
|
38
|
+
- id: allow-workspace-writes
|
|
39
|
+
when:
|
|
40
|
+
tool_type: file_write
|
|
41
|
+
target_matches: ["./workspace/**", "./src/**", "./tests/**"]
|
|
42
|
+
effect: allow
|
|
43
|
+
|
|
44
|
+
- id: allow-local-reads
|
|
45
|
+
when:
|
|
46
|
+
tool_type: file_read
|
|
47
|
+
effect: allow
|
|
48
|
+
|
|
49
|
+
- id: approval-payments
|
|
50
|
+
when:
|
|
51
|
+
tool_type: payment
|
|
52
|
+
effect: require_approval
|
|
53
|
+
reason: All payments require human approval
|
|
54
|
+
|
|
55
|
+
- id: approval-external-email
|
|
56
|
+
when:
|
|
57
|
+
tool_type: email
|
|
58
|
+
effect: require_approval
|
|
59
|
+
reason: Outgoing email requires approval
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
|
|
3
|
+
defaults:
|
|
4
|
+
approval_threshold: 0.85
|
|
5
|
+
block_threshold: 0.98
|
|
6
|
+
|
|
7
|
+
rules:
|
|
8
|
+
- id: block-prod-paths
|
|
9
|
+
when:
|
|
10
|
+
target_matches: ["/prod/**", "/production/**"]
|
|
11
|
+
effect: deny
|
|
12
|
+
reason: Never touch production paths from dev
|
|
13
|
+
|
|
14
|
+
- id: allow-all-reads
|
|
15
|
+
when:
|
|
16
|
+
tool_type: file_read
|
|
17
|
+
effect: allow
|
|
18
|
+
|
|
19
|
+
- id: allow-workspace
|
|
20
|
+
when:
|
|
21
|
+
tool_type: file_write
|
|
22
|
+
target_matches: ["./**"]
|
|
23
|
+
effect: allow
|
|
24
|
+
|
|
25
|
+
- id: rewrite-destructive-delete
|
|
26
|
+
when:
|
|
27
|
+
tool_type: shell
|
|
28
|
+
action_matches: ["rm -rf"]
|
|
29
|
+
effect: allow_with_rewrite
|
|
30
|
+
rewrite:
|
|
31
|
+
tool_type: file_delete
|
|
32
|
+
action: move_to_trash
|
|
33
|
+
reason: Use trash instead of hard delete even in dev
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
version: 1
|
|
2
|
+
|
|
3
|
+
defaults:
|
|
4
|
+
approval_threshold: 0.40
|
|
5
|
+
block_threshold: 0.80
|
|
6
|
+
|
|
7
|
+
rules:
|
|
8
|
+
- id: approval-all-shell
|
|
9
|
+
when:
|
|
10
|
+
tool_type: shell
|
|
11
|
+
effect: require_approval
|
|
12
|
+
reason: All shell commands require approval under strict policy
|
|
13
|
+
|
|
14
|
+
- id: approval-all-http
|
|
15
|
+
when:
|
|
16
|
+
tool_type: http
|
|
17
|
+
domain_not_in_allowlist: true
|
|
18
|
+
allowlist:
|
|
19
|
+
- api.github.com
|
|
20
|
+
- api.anthropic.com
|
|
21
|
+
- api.openai.com
|
|
22
|
+
effect: require_approval
|
|
23
|
+
reason: HTTP calls outside the allowlist require approval
|
|
24
|
+
|
|
25
|
+
- id: block-destructive
|
|
26
|
+
when:
|
|
27
|
+
tool_type: shell
|
|
28
|
+
action_matches: ["rm -rf", "drop table", "truncate"]
|
|
29
|
+
effect: deny
|
|
30
|
+
reason: Destructive commands are never allowed
|
|
31
|
+
|
|
32
|
+
- id: block-secrets
|
|
33
|
+
when:
|
|
34
|
+
action_matches: [".env", "secrets", "credentials", "id_rsa"]
|
|
35
|
+
effect: deny
|
|
36
|
+
reason: Secrets must never be touched by an agent
|
|
37
|
+
|
|
38
|
+
- id: approval-payment
|
|
39
|
+
when:
|
|
40
|
+
tool_type: payment
|
|
41
|
+
effect: deny
|
|
42
|
+
reason: Agents cannot make payments in strict mode
|
package/package.json
CHANGED
|
@@ -1,22 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-lint",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Lint MCP server tool schemas for cross-client compatibility",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Lint MCP server tool schemas for cross-client compatibility + runtime preflight for agent tool calls",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"mcp-lint": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"main": "./dist/core/engine.js",
|
|
10
10
|
"types": "./dist/core/engine.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/core/engine.d.ts",
|
|
14
|
+
"default": "./dist/core/engine.js"
|
|
15
|
+
},
|
|
16
|
+
"./preflight": {
|
|
17
|
+
"types": "./dist/preflight/index.d.ts",
|
|
18
|
+
"default": "./dist/preflight/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./rules": {
|
|
21
|
+
"types": "./dist/rules/index.d.ts",
|
|
22
|
+
"default": "./dist/rules/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
11
25
|
"files": [
|
|
12
|
-
"dist/"
|
|
26
|
+
"dist/",
|
|
27
|
+
"examples/"
|
|
13
28
|
],
|
|
14
29
|
"keywords": [
|
|
15
30
|
"mcp",
|
|
16
31
|
"model-context-protocol",
|
|
17
32
|
"lint",
|
|
18
33
|
"schema",
|
|
19
|
-
"validation"
|
|
34
|
+
"validation",
|
|
35
|
+
"preflight",
|
|
36
|
+
"agent-safety",
|
|
37
|
+
"policy",
|
|
38
|
+
"guardrails"
|
|
20
39
|
],
|
|
21
40
|
"license": "MIT",
|
|
22
41
|
"engines": {
|