mcp-lint 0.3.0 → 0.5.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.
@@ -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,4 @@
1
+ export { preflight } from './engine.js';
2
+ export { loadPolicy, parsePolicy, matchRule } from './policy.js';
3
+ export { scoreAction } from './scorer.js';
4
+ //# sourceMappingURL=index.js.map
@@ -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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,IAAI,MAAM,MAAM,CAAC;AAGxB,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY;IAC3C,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoB,CAAC;IAClD,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoB,CAAC;IACnD,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC;AACjC,CAAC;AAED,SAAS,eAAe,CAAC,MAAuB;IAC9C,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9D,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;QAC1C,IAAI,CAAC,IAAI,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,GAAG,eAAe,CAAC,CAAC;QAClE,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,EAAE,kBAAkB,CAAC,CAAC;QAC3E,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,EAAE,oBAAoB,CAAC,CAAC;IACjF,CAAC;IACD,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,CAAC;QAC5B,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,EAAE;QAC/B,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE;QAC7B,KAAK;KACN,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAgB,EAAE,MAAc,EAAE,SAAiB;IAC3E,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;IAEpB,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;QAChB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACvE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;YAAE,OAAO,KAAK,CAAC;IACtD,CAAC;IAED,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;QAChB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACvE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;YAAE,OAAO,KAAK,CAAC;IACtD,CAAC;IAED,IAAI,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,GAAG,GAAG,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,IAAI,CAAC,GAAG;YAAE,OAAO,KAAK,CAAC;IACzB,CAAC;IAED,IAAI,CAAC,CAAC,cAAc,IAAI,CAAC,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;QACjE,IAAI,CAAC,GAAG;YAAE,OAAO,KAAK,CAAC;IACzB,CAAC;IAED,IAAI,CAAC,CAAC,uBAAuB,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC;QAChC,IAAI,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC1B,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;IAC3C,CAAC;IAED,IAAI,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACnC,IAAI,SAAS,GAAG,CAAC,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;IAC3C,CAAC;IAED,IAAI,OAAO,CAAC,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,CAAC,kBAAkB,IAAI,CAAC,CAAC;QAC5C,IAAI,IAAI,IAAI,CAAC,CAAC,YAAY;YAAE,OAAO,KAAK,CAAC;IAC3C,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAE,OAAe;IACjD,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,IAAI,MAAM,CACtB,GAAG;YACD,OAAO;iBACJ,OAAO,CAAC,oBAAoB,EAAE,MAAM,CAAC;iBACrC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC;iBACtB,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC;iBACvB,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC;YACvB,GAAG,CACN,CAAC;QACF,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AACjC,CAAC;AAED,SAAS,aAAa,CAAC,MAAc;IACnC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC;IACjD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IACvD,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IACzC,IAAI,MAAM,CAAC,OAAO,IAAI,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAChE,OAAQ,MAAM,CAAC,OAAO,CAAC,MAAiB,CAAC,WAAW,EAAE,CAAC;IACzD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Action, Reversibility } from './types.js';
2
+ export interface ScoreBreakdown {
3
+ score: number;
4
+ reasons: string[];
5
+ reversibility: Reversibility;
6
+ }
7
+ export declare function scoreAction(action: Action): ScoreBreakdown;
@@ -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":"AAEA,MAAM,WAAW,GAA6B;IAC5C,SAAS,EAAE,GAAG;IACd,UAAU,EAAE,IAAI;IAChB,WAAW,EAAE,IAAI;IACjB,KAAK,EAAE,GAAG;IACV,IAAI,EAAE,IAAI;IACV,QAAQ,EAAE,GAAG;IACb,KAAK,EAAE,GAAG;IACV,OAAO,EAAE,IAAI;IACb,QAAQ,EAAE,GAAG;IACb,KAAK,EAAE,GAAG;CACX,CAAC;AAEF,MAAM,oBAAoB,GAAa;IACrC,eAAe;IACf,kBAAkB;IAClB,gBAAgB;IAChB,mBAAmB;IACnB,uBAAuB;IACvB,mBAAmB;IACnB,WAAW;IACX,0BAA0B;IAC1B,6BAA6B;IAC7B,2BAA2B;CAC5B,CAAC;AAEF,MAAM,iBAAiB,GAAa;IAClC,SAAS;IACT,UAAU;IACV,gBAAgB;IAChB,eAAe;IACf,aAAa;IACb,eAAe;IACf,mBAAmB;IACnB,UAAU;IACV,kBAAkB;CACnB,CAAC;AAEF,MAAM,uBAAuB,GAAa;IACxC,UAAU;IACV,gBAAgB;IAChB,SAAS;IACT,SAAS;IACT,SAAS;CACV,CAAC;AAQF,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC;IACjD,OAAO,CAAC,IAAI,CAAC,QAAQ,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;IAEzC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;IACvC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC;IACvC,MAAM,QAAQ,GAAG,GAAG,UAAU,IAAI,UAAU,EAAE,CAAC;IAE/C,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,KAAK,MAAM,OAAO,IAAI,oBAAoB,EAAE,CAAC;QAC3C,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3B,WAAW,GAAG,IAAI,CAAC;YACnB,KAAK,IAAI,IAAI,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YACpC,MAAM;QACR,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,SAAS,KAAK,aAAa,EAAE,CAAC;QACvC,WAAW,GAAG,IAAI,CAAC;IACrB,CAAC;IAED,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;QACxC,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3B,KAAK,IAAI,GAAG,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;YACjC,MAAM;QACR,CAAC;IACH,CAAC;IAED,KAAK,MAAM,OAAO,IAAI,uBAAuB,EAAE,CAAC;QAC9C,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3B,KAAK,IAAI,GAAG,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAC/B,MAAM;QACR,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,WAAW,KAAK,YAAY,EAAE,CAAC;QAC3F,KAAK,IAAI,IAAI,CAAC;QACd,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACnC,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,kBAAkB,IAAI,CAAC,CAAC;IAC5C,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;QACf,KAAK,IAAI,IAAI,CAAC;QACd,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC5B,CAAC;SAAM,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;QACrB,KAAK,IAAI,IAAI,CAAC;QACd,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IAExC,MAAM,aAAa,GAAG,oBAAoB,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAEhE,IAAI,aAAa,KAAK,cAAc,EAAE,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;IACzC,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;AAC3C,CAAC;AAED,SAAS,oBAAoB,CAAC,MAAc,EAAE,WAAoB;IAChE,IAAI,WAAW;QAAE,OAAO,cAAc,CAAC;IACvC,QAAQ,MAAM,CAAC,SAAS,EAAE,CAAC;QACzB,KAAK,WAAW;YACd,OAAO,YAAY,CAAC;QACtB,KAAK,YAAY;YACf,OAAO,sBAAsB,CAAC;QAChC,KAAK,aAAa;YAChB,OAAO,cAAc,CAAC;QACxB,KAAK,SAAS,CAAC;QACf,KAAK,OAAO;YACV,OAAO,cAAc,CAAC;QACxB,KAAK,MAAM;YACT,OAAO,sBAAsB,CAAC;QAChC,KAAK,UAAU;YACb,OAAO,sBAAsB,CAAC;QAChC,KAAK,OAAO;YACV,OAAO,sBAAsB,CAAC;QAChC,KAAK,UAAU;YACb,OAAO,sBAAsB,CAAC;QAChC;YACE,OAAO,sBAAsB,CAAC;IAClC,CAAC;AACH,CAAC"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/preflight/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ {
2
+ "actor_id": "agent:claude-code",
3
+ "workspace_id": "team-alpha",
4
+ "tool_type": "shell",
5
+ "tool_name": "bash",
6
+ "action": "rm -rf /prod/data",
7
+ "target": "/prod/data",
8
+ "context": {
9
+ "environment": "prod"
10
+ }
11
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "actor_id": "agent:claude-code",
3
+ "workspace_id": "team-alpha",
4
+ "tool_type": "shell",
5
+ "tool_name": "bash",
6
+ "action": "rm -rf ./tmp",
7
+ "target": "./tmp"
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "actor_id": "agent:claude-code",
3
+ "workspace_id": "team-alpha",
4
+ "tool_type": "file_read",
5
+ "tool_name": "read",
6
+ "action": "cat package.json",
7
+ "target": "./package.json"
8
+ }
@@ -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