mcp-lint 0.1.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/LICENSE +21 -0
- package/README.md +283 -0
- package/dist/cli/commands/check.d.ts +2 -0
- package/dist/cli/commands/check.js +89 -0
- package/dist/cli/commands/check.js.map +1 -0
- package/dist/cli/commands/fix.d.ts +2 -0
- package/dist/cli/commands/fix.js +60 -0
- package/dist/cli/commands/fix.js.map +1 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.js +42 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/formatters/json.d.ts +2 -0
- package/dist/cli/formatters/json.js +4 -0
- package/dist/cli/formatters/json.js.map +1 -0
- package/dist/cli/formatters/markdown.d.ts +2 -0
- package/dist/cli/formatters/markdown.js +44 -0
- package/dist/cli/formatters/markdown.js.map +1 -0
- package/dist/cli/formatters/terminal.d.ts +2 -0
- package/dist/cli/formatters/terminal.js +48 -0
- package/dist/cli/formatters/terminal.js.map +1 -0
- package/dist/config/config.d.ts +3 -0
- package/dist/config/config.js +38 -0
- package/dist/config/config.js.map +1 -0
- package/dist/core/diagnostic.d.ts +15 -0
- package/dist/core/diagnostic.js +2 -0
- package/dist/core/diagnostic.js.map +1 -0
- package/dist/core/engine.d.ts +8 -0
- package/dist/core/engine.js +35 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/fixer.d.ts +3 -0
- package/dist/core/fixer.js +37 -0
- package/dist/core/fixer.js.map +1 -0
- package/dist/core/rule.d.ts +52 -0
- package/dist/core/rule.js +2 -0
- package/dist/core/rule.js.map +1 -0
- package/dist/core/severity.d.ts +1 -0
- package/dist/core/severity.js +2 -0
- package/dist/core/severity.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/loaders/file-loader.d.ts +2 -0
- package/dist/loaders/file-loader.js +30 -0
- package/dist/loaders/file-loader.js.map +1 -0
- package/dist/loaders/sse-loader.d.ts +2 -0
- package/dist/loaders/sse-loader.js +19 -0
- package/dist/loaders/sse-loader.js.map +1 -0
- package/dist/loaders/stdio-loader.d.ts +2 -0
- package/dist/loaders/stdio-loader.js +19 -0
- package/dist/loaders/stdio-loader.js.map +1 -0
- package/dist/rules/client-specific/claude.d.ts +2 -0
- package/dist/rules/client-specific/claude.js +30 -0
- package/dist/rules/client-specific/claude.js.map +1 -0
- package/dist/rules/client-specific/cursor.d.ts +2 -0
- package/dist/rules/client-specific/cursor.js +49 -0
- package/dist/rules/client-specific/cursor.js.map +1 -0
- package/dist/rules/client-specific/gemini.d.ts +3 -0
- package/dist/rules/client-specific/gemini.js +60 -0
- package/dist/rules/client-specific/gemini.js.map +1 -0
- package/dist/rules/client-specific/vscode.d.ts +2 -0
- package/dist/rules/client-specific/vscode.js +22 -0
- package/dist/rules/client-specific/vscode.js.map +1 -0
- package/dist/rules/description-exists.d.ts +2 -0
- package/dist/rules/description-exists.js +35 -0
- package/dist/rules/description-exists.js.map +1 -0
- package/dist/rules/index.d.ts +2 -0
- package/dist/rules/index.js +28 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/max-depth.d.ts +2 -0
- package/dist/rules/max-depth.js +59 -0
- package/dist/rules/max-depth.js.map +1 -0
- package/dist/rules/no-content-encoding.d.ts +2 -0
- package/dist/rules/no-content-encoding.js +33 -0
- package/dist/rules/no-content-encoding.js.map +1 -0
- package/dist/rules/no-empty-enum.d.ts +2 -0
- package/dist/rules/no-empty-enum.js +33 -0
- package/dist/rules/no-empty-enum.js.map +1 -0
- package/dist/rules/no-recursive-refs.d.ts +2 -0
- package/dist/rules/no-recursive-refs.js +68 -0
- package/dist/rules/no-recursive-refs.js.map +1 -0
- package/dist/rules/no-required-false.d.ts +2 -0
- package/dist/rules/no-required-false.js +33 -0
- package/dist/rules/no-required-false.js.map +1 -0
- package/dist/rules/no-unsupported-formats.d.ts +2 -0
- package/dist/rules/no-unsupported-formats.js +39 -0
- package/dist/rules/no-unsupported-formats.js.map +1 -0
- package/dist/rules/valid-json-schema-subset.d.ts +2 -0
- package/dist/rules/valid-json-schema-subset.js +43 -0
- package/dist/rules/valid-json-schema-subset.js.map +1 -0
- package/dist/utils/schema-walker.d.ts +3 -0
- package/dist/utils/schema-walker.js +52 -0
- package/dist/utils/schema-walker.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { noRequiredFalse } from './no-required-false.js';
|
|
2
|
+
import { noContentEncoding } from './no-content-encoding.js';
|
|
3
|
+
import { descriptionExists } from './description-exists.js';
|
|
4
|
+
import { noEmptyEnum } from './no-empty-enum.js';
|
|
5
|
+
import { maxDepth } from './max-depth.js';
|
|
6
|
+
import { noRecursiveRefs } from './no-recursive-refs.js';
|
|
7
|
+
import { validJsonSchemaSubset } from './valid-json-schema-subset.js';
|
|
8
|
+
import { noUnsupportedFormats } from './no-unsupported-formats.js';
|
|
9
|
+
import { claudeNoTypeArray } from './client-specific/claude.js';
|
|
10
|
+
import { cursorNoDefaultWithoutType } from './client-specific/cursor.js';
|
|
11
|
+
import { geminiNoOptionalWithoutDefault, geminiNoNestedObjects } from './client-specific/gemini.js';
|
|
12
|
+
import { vscodeMaxParams } from './client-specific/vscode.js';
|
|
13
|
+
export const allRules = [
|
|
14
|
+
noRequiredFalse,
|
|
15
|
+
noContentEncoding,
|
|
16
|
+
descriptionExists,
|
|
17
|
+
noEmptyEnum,
|
|
18
|
+
maxDepth,
|
|
19
|
+
noRecursiveRefs,
|
|
20
|
+
validJsonSchemaSubset,
|
|
21
|
+
noUnsupportedFormats,
|
|
22
|
+
claudeNoTypeArray,
|
|
23
|
+
cursorNoDefaultWithoutType,
|
|
24
|
+
geminiNoOptionalWithoutDefault,
|
|
25
|
+
geminiNoNestedObjects,
|
|
26
|
+
vscodeMaxParams,
|
|
27
|
+
];
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AACzE,OAAO,EAAE,8BAA8B,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpG,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,MAAM,CAAC,MAAM,QAAQ,GAAW;IAC9B,eAAe;IACf,iBAAiB;IACjB,iBAAiB;IACjB,WAAW;IACX,QAAQ;IACR,eAAe;IACf,qBAAqB;IACrB,oBAAoB;IACpB,iBAAiB;IACjB,0BAA0B;IAC1B,8BAA8B;IAC9B,qBAAqB;IACrB,eAAe;CAChB,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function getMaxDepth(schema, depth = 0) {
|
|
2
|
+
if (!schema || typeof schema !== 'object')
|
|
3
|
+
return depth;
|
|
4
|
+
let max = depth;
|
|
5
|
+
if (schema.properties) {
|
|
6
|
+
for (const value of Object.values(schema.properties)) {
|
|
7
|
+
max = Math.max(max, getMaxDepth(value, depth + 1));
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
if (schema.items) {
|
|
11
|
+
if (Array.isArray(schema.items)) {
|
|
12
|
+
for (const item of schema.items) {
|
|
13
|
+
max = Math.max(max, getMaxDepth(item, depth + 1));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
max = Math.max(max, getMaxDepth(schema.items, depth + 1));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
for (const kw of ['anyOf', 'oneOf', 'allOf']) {
|
|
21
|
+
const arr = schema[kw];
|
|
22
|
+
if (Array.isArray(arr)) {
|
|
23
|
+
for (const s of arr) {
|
|
24
|
+
max = Math.max(max, getMaxDepth(s, depth + 1));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (schema.not)
|
|
29
|
+
max = Math.max(max, getMaxDepth(schema.not, depth + 1));
|
|
30
|
+
if (schema.if)
|
|
31
|
+
max = Math.max(max, getMaxDepth(schema.if, depth + 1));
|
|
32
|
+
if (schema.then)
|
|
33
|
+
max = Math.max(max, getMaxDepth(schema.then, depth + 1));
|
|
34
|
+
if (schema.else)
|
|
35
|
+
max = Math.max(max, getMaxDepth(schema.else, depth + 1));
|
|
36
|
+
return max;
|
|
37
|
+
}
|
|
38
|
+
export const maxDepth = {
|
|
39
|
+
id: 'max-depth',
|
|
40
|
+
severity: 'warning',
|
|
41
|
+
description: 'Deeply nested schemas confuse LLMs and increase token consumption.',
|
|
42
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
43
|
+
check(tool, context) {
|
|
44
|
+
const limit = context.config.maxDepth ?? 5;
|
|
45
|
+
const depth = getMaxDepth(tool.inputSchema);
|
|
46
|
+
if (depth > limit) {
|
|
47
|
+
return [{
|
|
48
|
+
ruleId: 'max-depth',
|
|
49
|
+
severity: 'warning',
|
|
50
|
+
message: `Tool "${tool.name}" schema has nesting depth of ${depth}, which exceeds the limit of ${limit}. Keep schemas flat for better LLM comprehension.`,
|
|
51
|
+
toolName: tool.name,
|
|
52
|
+
path: 'inputSchema',
|
|
53
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
54
|
+
}];
|
|
55
|
+
}
|
|
56
|
+
return [];
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=max-depth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"max-depth.js","sourceRoot":"","sources":["../../src/rules/max-depth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { walkSchema } from '../utils/schema-walker.js';
|
|
2
|
+
export const noContentEncoding = {
|
|
3
|
+
id: 'no-content-encoding',
|
|
4
|
+
severity: 'error',
|
|
5
|
+
description: '`contentEncoding` is not part of the JSON Schema subset used by MCP clients.',
|
|
6
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
7
|
+
check(tool, _context) {
|
|
8
|
+
const diagnostics = [];
|
|
9
|
+
walkSchema(tool.inputSchema, (schema, path) => {
|
|
10
|
+
if ('contentEncoding' in schema) {
|
|
11
|
+
const diagnosticPath = path ? `inputSchema.${path}` : 'inputSchema';
|
|
12
|
+
diagnostics.push({
|
|
13
|
+
ruleId: 'no-content-encoding',
|
|
14
|
+
severity: 'error',
|
|
15
|
+
message: `"contentEncoding" at "${diagnosticPath}" is not supported by MCP clients and causes parse failures in strict validators.`,
|
|
16
|
+
toolName: tool.name,
|
|
17
|
+
path: diagnosticPath,
|
|
18
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
19
|
+
fix: {
|
|
20
|
+
description: 'Remove `contentEncoding` field',
|
|
21
|
+
apply: (s) => {
|
|
22
|
+
const copy = { ...s };
|
|
23
|
+
delete copy['contentEncoding'];
|
|
24
|
+
return copy;
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return diagnostics;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=no-content-encoding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-content-encoding.js","sourceRoot":"","sources":["../../src/rules/no-content-encoding.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD,MAAM,CAAC,MAAM,iBAAiB,GAAS;IACrC,EAAE,EAAE,qBAAqB;IACzB,QAAQ,EAAE,OAAO;IACjB,WAAW,EAAE,8EAA8E;IAC3F,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;IAEjD,KAAK,CAAC,IAAa,EAAE,QAAqB;QACxC,MAAM,WAAW,GAAiB,EAAE,CAAC;QAErC,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE;YAC5C,IAAI,iBAAiB,IAAI,MAAM,EAAE,CAAC;gBAChC,MAAM,cAAc,GAAG,IAAI,CAAC,CAAC,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;gBACpE,WAAW,CAAC,IAAI,CAAC;oBACf,MAAM,EAAE,qBAAqB;oBAC7B,QAAQ,EAAE,OAAO;oBACjB,OAAO,EAAE,yBAAyB,cAAc,mFAAmF;oBACnI,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,IAAI,EAAE,cAAc;oBACpB,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;oBACjD,GAAG,EAAE;wBACH,WAAW,EAAE,gCAAgC;wBAC7C,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE;4BACX,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;4BACtB,OAAO,IAAI,CAAC,iBAAiB,CAAC,CAAC;4BAC/B,OAAO,IAAI,CAAC;wBACd,CAAC;qBACF;iBACF,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,WAAW,CAAC;IACrB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { walkSchema } from '../utils/schema-walker.js';
|
|
2
|
+
export const noEmptyEnum = {
|
|
3
|
+
id: 'no-empty-enum',
|
|
4
|
+
severity: 'error',
|
|
5
|
+
description: 'An empty `enum` array makes the parameter impossible to satisfy.',
|
|
6
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
7
|
+
check(tool, _context) {
|
|
8
|
+
const diagnostics = [];
|
|
9
|
+
walkSchema(tool.inputSchema, (schema, path) => {
|
|
10
|
+
if (Array.isArray(schema.enum) && schema.enum.length === 0) {
|
|
11
|
+
const diagnosticPath = path ? `inputSchema.${path}` : 'inputSchema';
|
|
12
|
+
diagnostics.push({
|
|
13
|
+
ruleId: 'no-empty-enum',
|
|
14
|
+
severity: 'error',
|
|
15
|
+
message: `Empty \`enum: []\` at "${diagnosticPath}" makes the parameter impossible to satisfy. This is likely a bug.`,
|
|
16
|
+
toolName: tool.name,
|
|
17
|
+
path: diagnosticPath,
|
|
18
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
19
|
+
fix: {
|
|
20
|
+
description: 'Remove the `enum` field',
|
|
21
|
+
apply: (s) => {
|
|
22
|
+
const copy = { ...s };
|
|
23
|
+
delete copy['enum'];
|
|
24
|
+
return copy;
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return diagnostics;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=no-empty-enum.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-empty-enum.js","sourceRoot":"","sources":["../../src/rules/no-empty-enum.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD,MAAM,CAAC,MAAM,WAAW,GAAS;IAC/B,EAAE,EAAE,eAAe;IACnB,QAAQ,EAAE,OAAO;IACjB,WAAW,EAAE,kEAAkE;IAC/E,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;IAEjD,KAAK,CAAC,IAAa,EAAE,QAAqB;QACxC,MAAM,WAAW,GAAiB,EAAE,CAAC;QAErC,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE;YAC5C,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC3D,MAAM,cAAc,GAAG,IAAI,CAAC,CAAC,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;gBACpE,WAAW,CAAC,IAAI,CAAC;oBACf,MAAM,EAAE,eAAe;oBACvB,QAAQ,EAAE,OAAO;oBACjB,OAAO,EAAE,0BAA0B,cAAc,oEAAoE;oBACrH,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,IAAI,EAAE,cAAc;oBACpB,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;oBACjD,GAAG,EAAE;wBACH,WAAW,EAAE,yBAAyB;wBACtC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE;4BACX,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;4BACtB,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;4BACpB,OAAO,IAAI,CAAC;wBACd,CAAC;qBACF;iBACF,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,WAAW,CAAC;IACrB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
function detectCycle(schema, allDefs, chain) {
|
|
2
|
+
if (!schema || typeof schema !== 'object')
|
|
3
|
+
return false;
|
|
4
|
+
if (typeof schema.$ref === 'string') {
|
|
5
|
+
const match = schema.$ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/);
|
|
6
|
+
if (match) {
|
|
7
|
+
const defName = match[1];
|
|
8
|
+
if (chain.has(defName))
|
|
9
|
+
return true;
|
|
10
|
+
const def = allDefs[defName];
|
|
11
|
+
if (def) {
|
|
12
|
+
return detectCycle(def, allDefs, new Set([...chain, defName]));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const subs = [
|
|
18
|
+
...(schema.properties ? Object.values(schema.properties) : []),
|
|
19
|
+
...(Array.isArray(schema.items) ? schema.items : schema.items ? [schema.items] : []),
|
|
20
|
+
...(schema.anyOf ?? []),
|
|
21
|
+
...(schema.oneOf ?? []),
|
|
22
|
+
...(schema.allOf ?? []),
|
|
23
|
+
schema.not,
|
|
24
|
+
schema.if,
|
|
25
|
+
schema.then,
|
|
26
|
+
schema.else,
|
|
27
|
+
];
|
|
28
|
+
return subs.filter((s) => s != null).some((s) => detectCycle(s, allDefs, chain));
|
|
29
|
+
}
|
|
30
|
+
export const noRecursiveRefs = {
|
|
31
|
+
id: 'no-recursive-refs',
|
|
32
|
+
severity: 'error',
|
|
33
|
+
description: 'Circular $ref causes infinite loops or crashes in most MCP clients.',
|
|
34
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
35
|
+
check(tool, _context) {
|
|
36
|
+
const schema = tool.inputSchema;
|
|
37
|
+
const allDefs = {
|
|
38
|
+
...(schema.$defs ?? {}),
|
|
39
|
+
...(schema.definitions ?? {}),
|
|
40
|
+
};
|
|
41
|
+
// Check from root
|
|
42
|
+
if (detectCycle(schema, allDefs, new Set())) {
|
|
43
|
+
return [{
|
|
44
|
+
ruleId: 'no-recursive-refs',
|
|
45
|
+
severity: 'error',
|
|
46
|
+
message: `Tool "${tool.name}" schema contains a circular $ref that could cause infinite loops or crashes in MCP clients.`,
|
|
47
|
+
toolName: tool.name,
|
|
48
|
+
path: 'inputSchema',
|
|
49
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
50
|
+
}];
|
|
51
|
+
}
|
|
52
|
+
// Check definitions not reachable from root
|
|
53
|
+
for (const [defName, def] of Object.entries(allDefs)) {
|
|
54
|
+
if (detectCycle(def, allDefs, new Set([defName]))) {
|
|
55
|
+
return [{
|
|
56
|
+
ruleId: 'no-recursive-refs',
|
|
57
|
+
severity: 'error',
|
|
58
|
+
message: `Definition "$defs.${defName}" in tool "${tool.name}" contains a circular $ref.`,
|
|
59
|
+
toolName: tool.name,
|
|
60
|
+
path: `inputSchema.$defs.${defName}`,
|
|
61
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
62
|
+
}];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return [];
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=no-recursive-refs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-recursive-refs.js","sourceRoot":"","sources":["../../src/rules/no-recursive-refs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { walkSchema } from '../utils/schema-walker.js';
|
|
2
|
+
export const noRequiredFalse = {
|
|
3
|
+
id: 'no-required-false',
|
|
4
|
+
severity: 'error',
|
|
5
|
+
description: '`required: false` on a property is not valid JSON Schema. Properties are optional by default.',
|
|
6
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
7
|
+
check(tool, _context) {
|
|
8
|
+
const diagnostics = [];
|
|
9
|
+
walkSchema(tool.inputSchema, (schema, path) => {
|
|
10
|
+
if (schema.required === false) {
|
|
11
|
+
const diagnosticPath = path ? `inputSchema.${path}` : 'inputSchema';
|
|
12
|
+
diagnostics.push({
|
|
13
|
+
ruleId: 'no-required-false',
|
|
14
|
+
severity: 'error',
|
|
15
|
+
message: `Schema node at "${diagnosticPath}" has \`required: false\` which is not valid JSON Schema. Remove it — properties are optional by default unless listed in the parent's \`required\` array.`,
|
|
16
|
+
toolName: tool.name,
|
|
17
|
+
path: diagnosticPath,
|
|
18
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
19
|
+
fix: {
|
|
20
|
+
description: 'Remove `required: false` from the property',
|
|
21
|
+
apply: (s) => {
|
|
22
|
+
const copy = { ...s };
|
|
23
|
+
delete copy['required'];
|
|
24
|
+
return copy;
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return diagnostics;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=no-required-false.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-required-false.js","sourceRoot":"","sources":["../../src/rules/no-required-false.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD,MAAM,CAAC,MAAM,eAAe,GAAS;IACnC,EAAE,EAAE,mBAAmB;IACvB,QAAQ,EAAE,OAAO;IACjB,WAAW,EAAE,+FAA+F;IAC5G,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;IAEjD,KAAK,CAAC,IAAa,EAAE,QAAqB;QACxC,MAAM,WAAW,GAAiB,EAAE,CAAC;QAErC,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE;YAC5C,IAAK,MAAkC,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;gBAC3D,MAAM,cAAc,GAAG,IAAI,CAAC,CAAC,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;gBACpE,WAAW,CAAC,IAAI,CAAC;oBACf,MAAM,EAAE,mBAAmB;oBAC3B,QAAQ,EAAE,OAAO;oBACjB,OAAO,EAAE,mBAAmB,cAAc,4JAA4J;oBACtM,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,IAAI,EAAE,cAAc;oBACpB,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;oBACjD,GAAG,EAAE;wBACH,WAAW,EAAE,4CAA4C;wBACzD,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE;4BACX,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;4BACtB,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC;4BACxB,OAAO,IAAI,CAAC;wBACd,CAAC;qBACF;iBACF,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,WAAW,CAAC;IACrB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { walkSchema } from '../utils/schema-walker.js';
|
|
2
|
+
const UNSUPPORTED_FORMATS = new Set([
|
|
3
|
+
'uri-reference', 'iri', 'iri-reference',
|
|
4
|
+
'idn-email', 'idn-hostname',
|
|
5
|
+
'json-pointer', 'relative-json-pointer',
|
|
6
|
+
'regex',
|
|
7
|
+
]);
|
|
8
|
+
export const noUnsupportedFormats = {
|
|
9
|
+
id: 'no-unsupported-formats',
|
|
10
|
+
severity: 'warning',
|
|
11
|
+
description: 'Some `format` values are not widely supported and may be rejected by strict clients.',
|
|
12
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
13
|
+
check(tool, _context) {
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
walkSchema(tool.inputSchema, (schema, path) => {
|
|
16
|
+
if (typeof schema.format === 'string' && UNSUPPORTED_FORMATS.has(schema.format)) {
|
|
17
|
+
const diagnosticPath = path ? `inputSchema.${path}` : 'inputSchema';
|
|
18
|
+
diagnostics.push({
|
|
19
|
+
ruleId: 'no-unsupported-formats',
|
|
20
|
+
severity: 'warning',
|
|
21
|
+
message: `Format "${schema.format}" at "${diagnosticPath}" is not widely supported across MCP clients. Use a plain string without format validation.`,
|
|
22
|
+
toolName: tool.name,
|
|
23
|
+
path: diagnosticPath,
|
|
24
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
25
|
+
fix: {
|
|
26
|
+
description: `Remove the \`format: "${schema.format}"\` field`,
|
|
27
|
+
apply: (s) => {
|
|
28
|
+
const copy = { ...s };
|
|
29
|
+
delete copy['format'];
|
|
30
|
+
return copy;
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return diagnostics;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=no-unsupported-formats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-unsupported-formats.js","sourceRoot":"","sources":["../../src/rules/no-unsupported-formats.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAClC,eAAe,EAAE,KAAK,EAAE,eAAe;IACvC,WAAW,EAAE,cAAc;IAC3B,cAAc,EAAE,uBAAuB;IACvC,OAAO;CACR,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAS;IACxC,EAAE,EAAE,wBAAwB;IAC5B,QAAQ,EAAE,SAAS;IACnB,WAAW,EAAE,sFAAsF;IACnG,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;IAEjD,KAAK,CAAC,IAAa,EAAE,QAAqB;QACxC,MAAM,WAAW,GAAiB,EAAE,CAAC;QAErC,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE;YAC5C,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,mBAAmB,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;gBAChF,MAAM,cAAc,GAAG,IAAI,CAAC,CAAC,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;gBACpE,WAAW,CAAC,IAAI,CAAC;oBACf,MAAM,EAAE,wBAAwB;oBAChC,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,WAAW,MAAM,CAAC,MAAM,SAAS,cAAc,6FAA6F;oBACrJ,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,IAAI,EAAE,cAAc;oBACpB,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;oBACjD,GAAG,EAAE;wBACH,WAAW,EAAE,yBAAyB,MAAM,CAAC,MAAM,WAAW;wBAC9D,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE;4BACX,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;4BACtB,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC;4BACtB,OAAO,IAAI,CAAC;wBACd,CAAC;qBACF;iBACF,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,WAAW,CAAC;IACrB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { walkSchema } from '../utils/schema-walker.js';
|
|
2
|
+
const UNSUPPORTED_KEYWORDS = [
|
|
3
|
+
'if', 'then', 'else',
|
|
4
|
+
'oneOf', 'anyOf', 'allOf', 'not',
|
|
5
|
+
'patternProperties', 'additionalItems',
|
|
6
|
+
'dependencies', '$dynamicRef',
|
|
7
|
+
];
|
|
8
|
+
export const validJsonSchemaSubset = {
|
|
9
|
+
id: 'valid-json-schema-subset',
|
|
10
|
+
severity: 'error',
|
|
11
|
+
description: 'MCP tool schemas should use a simple JSON Schema subset. Advanced keywords are not reliably supported.',
|
|
12
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
13
|
+
check(tool, _context) {
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
walkSchema(tool.inputSchema, (schema, path) => {
|
|
16
|
+
const diagnosticPath = path ? `inputSchema.${path}` : 'inputSchema';
|
|
17
|
+
for (const keyword of UNSUPPORTED_KEYWORDS) {
|
|
18
|
+
if (keyword in schema) {
|
|
19
|
+
diagnostics.push({
|
|
20
|
+
ruleId: 'valid-json-schema-subset',
|
|
21
|
+
severity: 'error',
|
|
22
|
+
message: `Keyword "${keyword}" at "${diagnosticPath}" is not in the MCP-supported JSON Schema subset and is not reliably supported across clients.`,
|
|
23
|
+
toolName: tool.name,
|
|
24
|
+
path: diagnosticPath,
|
|
25
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (typeof schema.$ref === 'string' && !schema.$ref.startsWith('#')) {
|
|
30
|
+
diagnostics.push({
|
|
31
|
+
ruleId: 'valid-json-schema-subset',
|
|
32
|
+
severity: 'error',
|
|
33
|
+
message: `External $ref "${schema.$ref}" at "${diagnosticPath}" is not supported. Only internal refs (#/...) are allowed.`,
|
|
34
|
+
toolName: tool.name,
|
|
35
|
+
path: diagnosticPath,
|
|
36
|
+
clients: ['claude', 'cursor', 'gemini', 'vscode'],
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return diagnostics;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=valid-json-schema-subset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"valid-json-schema-subset.js","sourceRoot":"","sources":["../../src/rules/valid-json-schema-subset.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAEvD,MAAM,oBAAoB,GAAG;IAC3B,IAAI,EAAE,MAAM,EAAE,MAAM;IACpB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK;IAChC,mBAAmB,EAAE,iBAAiB;IACtC,cAAc,EAAE,aAAa;CACrB,CAAC;AAEX,MAAM,CAAC,MAAM,qBAAqB,GAAS;IACzC,EAAE,EAAE,0BAA0B;IAC9B,QAAQ,EAAE,OAAO;IACjB,WAAW,EAAE,wGAAwG;IACrH,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;IAEjD,KAAK,CAAC,IAAa,EAAE,QAAqB;QACxC,MAAM,WAAW,GAAiB,EAAE,CAAC;QAErC,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE;YAC5C,MAAM,cAAc,GAAG,IAAI,CAAC,CAAC,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;YAEpE,KAAK,MAAM,OAAO,IAAI,oBAAoB,EAAE,CAAC;gBAC3C,IAAI,OAAO,IAAI,MAAM,EAAE,CAAC;oBACtB,WAAW,CAAC,IAAI,CAAC;wBACf,MAAM,EAAE,0BAA0B;wBAClC,QAAQ,EAAE,OAAO;wBACjB,OAAO,EAAE,YAAY,OAAO,SAAS,cAAc,gGAAgG;wBACnJ,QAAQ,EAAE,IAAI,CAAC,IAAI;wBACnB,IAAI,EAAE,cAAc;wBACpB,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;qBAClD,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAED,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACpE,WAAW,CAAC,IAAI,CAAC;oBACf,MAAM,EAAE,0BAA0B;oBAClC,QAAQ,EAAE,OAAO;oBACjB,OAAO,EAAE,kBAAkB,MAAM,CAAC,IAAI,SAAS,cAAc,6DAA6D;oBAC1H,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,IAAI,EAAE,cAAc;oBACpB,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;iBAClD,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,WAAW,CAAC;IACrB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
function walkInternal(schema, visitor, path, visited) {
|
|
2
|
+
if (!schema || typeof schema !== 'object')
|
|
3
|
+
return;
|
|
4
|
+
if (visited.has(schema))
|
|
5
|
+
return;
|
|
6
|
+
visited.add(schema);
|
|
7
|
+
visitor(schema, path);
|
|
8
|
+
if (schema.properties) {
|
|
9
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
10
|
+
const childPath = path ? `${path}.properties.${key}` : `properties.${key}`;
|
|
11
|
+
walkInternal(value, visitor, childPath, visited);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
if (schema.items) {
|
|
15
|
+
if (Array.isArray(schema.items)) {
|
|
16
|
+
schema.items.forEach((item, i) => {
|
|
17
|
+
const childPath = path ? `${path}.items[${i}]` : `items[${i}]`;
|
|
18
|
+
walkInternal(item, visitor, childPath, visited);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
walkInternal(schema.items, visitor, path ? `${path}.items` : 'items', visited);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const kw of ['anyOf', 'oneOf', 'allOf']) {
|
|
26
|
+
const arr = schema[kw];
|
|
27
|
+
if (Array.isArray(arr)) {
|
|
28
|
+
arr.forEach((s, i) => {
|
|
29
|
+
const childPath = path ? `${path}.${kw}[${i}]` : `${kw}[${i}]`;
|
|
30
|
+
walkInternal(s, visitor, childPath, visited);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (schema.not)
|
|
35
|
+
walkInternal(schema.not, visitor, path ? `${path}.not` : 'not', visited);
|
|
36
|
+
if (schema.if)
|
|
37
|
+
walkInternal(schema.if, visitor, path ? `${path}.if` : 'if', visited);
|
|
38
|
+
if (schema.then)
|
|
39
|
+
walkInternal(schema.then, visitor, path ? `${path}.then` : 'then', visited);
|
|
40
|
+
if (schema.else)
|
|
41
|
+
walkInternal(schema.else, visitor, path ? `${path}.else` : 'else', visited);
|
|
42
|
+
if (schema.patternProperties) {
|
|
43
|
+
for (const [pattern, value] of Object.entries(schema.patternProperties)) {
|
|
44
|
+
const childPath = path ? `${path}.patternProperties.${pattern}` : `patternProperties.${pattern}`;
|
|
45
|
+
walkInternal(value, visitor, childPath, visited);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function walkSchema(schema, visitor, path = '') {
|
|
50
|
+
walkInternal(schema, visitor, path, new WeakSet());
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=schema-walker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-walker.js","sourceRoot":"","sources":["../../src/utils/schema-walker.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-lint",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lint MCP server tool schemas for cross-client compatibility",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mcp-lint": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/core/engine.js",
|
|
10
|
+
"types": "./dist/core/engine.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"model-context-protocol",
|
|
17
|
+
"lint",
|
|
18
|
+
"schema",
|
|
19
|
+
"validation"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"dev": "tsc --watch",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"test:coverage": "vitest run --coverage",
|
|
31
|
+
"prepublishOnly": "npm run build && npm run test"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
35
|
+
"chalk": "^5.6.2",
|
|
36
|
+
"commander": "^14.0.3",
|
|
37
|
+
"yaml": "^2.8.3"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^25.5.0",
|
|
41
|
+
"typescript": "^6.0.2",
|
|
42
|
+
"vitest": "^4.1.1"
|
|
43
|
+
}
|
|
44
|
+
}
|