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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +283 -0
  3. package/dist/cli/commands/check.d.ts +2 -0
  4. package/dist/cli/commands/check.js +89 -0
  5. package/dist/cli/commands/check.js.map +1 -0
  6. package/dist/cli/commands/fix.d.ts +2 -0
  7. package/dist/cli/commands/fix.js +60 -0
  8. package/dist/cli/commands/fix.js.map +1 -0
  9. package/dist/cli/commands/init.d.ts +2 -0
  10. package/dist/cli/commands/init.js +42 -0
  11. package/dist/cli/commands/init.js.map +1 -0
  12. package/dist/cli/formatters/json.d.ts +2 -0
  13. package/dist/cli/formatters/json.js +4 -0
  14. package/dist/cli/formatters/json.js.map +1 -0
  15. package/dist/cli/formatters/markdown.d.ts +2 -0
  16. package/dist/cli/formatters/markdown.js +44 -0
  17. package/dist/cli/formatters/markdown.js.map +1 -0
  18. package/dist/cli/formatters/terminal.d.ts +2 -0
  19. package/dist/cli/formatters/terminal.js +48 -0
  20. package/dist/cli/formatters/terminal.js.map +1 -0
  21. package/dist/config/config.d.ts +3 -0
  22. package/dist/config/config.js +38 -0
  23. package/dist/config/config.js.map +1 -0
  24. package/dist/core/diagnostic.d.ts +15 -0
  25. package/dist/core/diagnostic.js +2 -0
  26. package/dist/core/diagnostic.js.map +1 -0
  27. package/dist/core/engine.d.ts +8 -0
  28. package/dist/core/engine.js +35 -0
  29. package/dist/core/engine.js.map +1 -0
  30. package/dist/core/fixer.d.ts +3 -0
  31. package/dist/core/fixer.js +37 -0
  32. package/dist/core/fixer.js.map +1 -0
  33. package/dist/core/rule.d.ts +52 -0
  34. package/dist/core/rule.js +2 -0
  35. package/dist/core/rule.js.map +1 -0
  36. package/dist/core/severity.d.ts +1 -0
  37. package/dist/core/severity.js +2 -0
  38. package/dist/core/severity.js.map +1 -0
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.js +15 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/loaders/file-loader.d.ts +2 -0
  43. package/dist/loaders/file-loader.js +30 -0
  44. package/dist/loaders/file-loader.js.map +1 -0
  45. package/dist/loaders/sse-loader.d.ts +2 -0
  46. package/dist/loaders/sse-loader.js +19 -0
  47. package/dist/loaders/sse-loader.js.map +1 -0
  48. package/dist/loaders/stdio-loader.d.ts +2 -0
  49. package/dist/loaders/stdio-loader.js +19 -0
  50. package/dist/loaders/stdio-loader.js.map +1 -0
  51. package/dist/rules/client-specific/claude.d.ts +2 -0
  52. package/dist/rules/client-specific/claude.js +30 -0
  53. package/dist/rules/client-specific/claude.js.map +1 -0
  54. package/dist/rules/client-specific/cursor.d.ts +2 -0
  55. package/dist/rules/client-specific/cursor.js +49 -0
  56. package/dist/rules/client-specific/cursor.js.map +1 -0
  57. package/dist/rules/client-specific/gemini.d.ts +3 -0
  58. package/dist/rules/client-specific/gemini.js +60 -0
  59. package/dist/rules/client-specific/gemini.js.map +1 -0
  60. package/dist/rules/client-specific/vscode.d.ts +2 -0
  61. package/dist/rules/client-specific/vscode.js +22 -0
  62. package/dist/rules/client-specific/vscode.js.map +1 -0
  63. package/dist/rules/description-exists.d.ts +2 -0
  64. package/dist/rules/description-exists.js +35 -0
  65. package/dist/rules/description-exists.js.map +1 -0
  66. package/dist/rules/index.d.ts +2 -0
  67. package/dist/rules/index.js +28 -0
  68. package/dist/rules/index.js.map +1 -0
  69. package/dist/rules/max-depth.d.ts +2 -0
  70. package/dist/rules/max-depth.js +59 -0
  71. package/dist/rules/max-depth.js.map +1 -0
  72. package/dist/rules/no-content-encoding.d.ts +2 -0
  73. package/dist/rules/no-content-encoding.js +33 -0
  74. package/dist/rules/no-content-encoding.js.map +1 -0
  75. package/dist/rules/no-empty-enum.d.ts +2 -0
  76. package/dist/rules/no-empty-enum.js +33 -0
  77. package/dist/rules/no-empty-enum.js.map +1 -0
  78. package/dist/rules/no-recursive-refs.d.ts +2 -0
  79. package/dist/rules/no-recursive-refs.js +68 -0
  80. package/dist/rules/no-recursive-refs.js.map +1 -0
  81. package/dist/rules/no-required-false.d.ts +2 -0
  82. package/dist/rules/no-required-false.js +33 -0
  83. package/dist/rules/no-required-false.js.map +1 -0
  84. package/dist/rules/no-unsupported-formats.d.ts +2 -0
  85. package/dist/rules/no-unsupported-formats.js +39 -0
  86. package/dist/rules/no-unsupported-formats.js.map +1 -0
  87. package/dist/rules/valid-json-schema-subset.d.ts +2 -0
  88. package/dist/rules/valid-json-schema-subset.js +43 -0
  89. package/dist/rules/valid-json-schema-subset.js.map +1 -0
  90. package/dist/utils/schema-walker.d.ts +3 -0
  91. package/dist/utils/schema-walker.js +52 -0
  92. package/dist/utils/schema-walker.js.map +1 -0
  93. 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,2 @@
1
+ import type { Rule } from '../core/rule.js';
2
+ export declare const maxDepth: Rule;
@@ -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,2 @@
1
+ import type { Rule } from '../core/rule.js';
2
+ export declare const noContentEncoding: Rule;
@@ -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,2 @@
1
+ import type { Rule } from '../core/rule.js';
2
+ export declare const noEmptyEnum: Rule;
@@ -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,2 @@
1
+ import type { Rule } from '../core/rule.js';
2
+ export declare const noRecursiveRefs: Rule;
@@ -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,2 @@
1
+ import type { Rule } from '../core/rule.js';
2
+ export declare const noRequiredFalse: Rule;
@@ -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,2 @@
1
+ import type { Rule } from '../core/rule.js';
2
+ export declare const noUnsupportedFormats: Rule;
@@ -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,2 @@
1
+ import type { Rule } from '../core/rule.js';
2
+ export declare const validJsonSchemaSubset: Rule;
@@ -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,3 @@
1
+ import type { JSONSchema } from '../core/rule.js';
2
+ export type SchemaVisitor = (schema: JSONSchema, path: string) => void;
3
+ export declare function walkSchema(schema: JSONSchema, visitor: SchemaVisitor, path?: string): void;
@@ -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
+ }