mcp-lint 0.1.0 → 0.2.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 (33) hide show
  1. package/dist/cli/commands/compat.d.ts +2 -0
  2. package/dist/cli/commands/compat.js +128 -0
  3. package/dist/cli/commands/compat.js.map +1 -0
  4. package/dist/cli/commands/explain.d.ts +2 -0
  5. package/dist/cli/commands/explain.js +50 -0
  6. package/dist/cli/commands/explain.js.map +1 -0
  7. package/dist/config/config.d.ts +1 -1
  8. package/dist/config/config.js +6 -3
  9. package/dist/config/config.js.map +1 -1
  10. package/dist/config/presets.d.ts +2 -0
  11. package/dist/config/presets.js +49 -0
  12. package/dist/config/presets.js.map +1 -0
  13. package/dist/core/compat-matrix.d.ts +13 -0
  14. package/dist/core/compat-matrix.js +31 -0
  15. package/dist/core/compat-matrix.js.map +1 -0
  16. package/dist/core/rule.d.ts +10 -1
  17. package/dist/index.js +5 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/rules/client-specific/cline.d.ts +2 -0
  20. package/dist/rules/client-specific/cline.js +49 -0
  21. package/dist/rules/client-specific/cline.js.map +1 -0
  22. package/dist/rules/client-specific/continue.d.ts +2 -0
  23. package/dist/rules/client-specific/continue.js +30 -0
  24. package/dist/rules/client-specific/continue.js.map +1 -0
  25. package/dist/rules/client-specific/openai.d.ts +3 -0
  26. package/dist/rules/client-specific/openai.js +59 -0
  27. package/dist/rules/client-specific/openai.js.map +1 -0
  28. package/dist/rules/client-specific/windsurf.d.ts +2 -0
  29. package/dist/rules/client-specific/windsurf.js +39 -0
  30. package/dist/rules/client-specific/windsurf.js.map +1 -0
  31. package/dist/rules/index.js +9 -0
  32. package/dist/rules/index.js.map +1 -1
  33. package/package.json +44 -44
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerCompatCommand(program: Command): void;
@@ -0,0 +1,128 @@
1
+ import { loadFile } from '../../loaders/file-loader.js';
2
+ import { loadStdio } from '../../loaders/stdio-loader.js';
3
+ import { loadSSE } from '../../loaders/sse-loader.js';
4
+ import { LintEngine } from '../../core/engine.js';
5
+ import { buildCompatMatrix } from '../../core/compat-matrix.js';
6
+ import { allRules } from '../../rules/index.js';
7
+ import { loadConfig, DEFAULT_CONFIG } from '../../config/config.js';
8
+ import chalk from 'chalk';
9
+ function formatCompatTerminal(matrix, noColor) {
10
+ if (noColor)
11
+ chalk.level = 0;
12
+ const { tools, clients, cells } = matrix;
13
+ if (tools.length === 0)
14
+ return chalk.yellow('No tools found.\n');
15
+ const TOOL_WIDTH = Math.max(...tools.map((t) => t.length), 12) + 2;
16
+ const COL_WIDTH = 10;
17
+ const header = ' '.repeat(TOOL_WIDTH) +
18
+ clients.map((c) => c.padEnd(COL_WIDTH)).join('');
19
+ const rows = tools.map((tool) => {
20
+ const cols = clients.map((client) => {
21
+ const cell = cells[tool][client];
22
+ return cell.compatible
23
+ ? chalk.green('✓'.padEnd(COL_WIDTH))
24
+ : chalk.red('✗'.padEnd(COL_WIDTH));
25
+ });
26
+ return tool.padEnd(TOOL_WIDTH) + cols.join('');
27
+ });
28
+ const incompatible = tools.filter((t) => clients.some((c) => !cells[t][c].compatible));
29
+ const summary = incompatible.length === 0
30
+ ? chalk.green('\nAll tools compatible across all clients.\n')
31
+ : chalk.yellow(`\n${incompatible.length} tool${incompatible.length !== 1 ? 's' : ''} have compatibility issues. Run \`mcp-lint check\` for details.\n`);
32
+ return ['\n', header, ...rows, summary].join('\n');
33
+ }
34
+ function formatCompatJson(matrix) {
35
+ const result = {
36
+ tools: matrix.tools,
37
+ clients: matrix.clients,
38
+ matrix: {},
39
+ };
40
+ for (const tool of matrix.tools) {
41
+ result.matrix[tool] = {};
42
+ for (const client of matrix.clients) {
43
+ const cell = matrix.cells[tool][client];
44
+ (result.matrix[tool])[client] = {
45
+ compatible: cell.compatible,
46
+ errorCount: cell.errorCount,
47
+ };
48
+ }
49
+ }
50
+ return JSON.stringify(result, null, 2) + '\n';
51
+ }
52
+ function formatCompatMarkdown(matrix) {
53
+ const { tools, clients, cells } = matrix;
54
+ if (tools.length === 0)
55
+ return '_No tools found._\n';
56
+ const header = `| Tool | ${clients.join(' | ')} |`;
57
+ const separator = `|------|${clients.map(() => '------|').join('')}`;
58
+ const rows = tools.map((tool) => {
59
+ const cols = clients.map((c) => (cells[tool][c].compatible ? '✅' : '❌'));
60
+ return `| ${tool} | ${cols.join(' | ')} |`;
61
+ });
62
+ const incompatible = tools.filter((t) => clients.some((c) => !cells[t][c].compatible));
63
+ const summary = incompatible.length === 0
64
+ ? '\n_All tools compatible across all clients._\n'
65
+ : `\n_${incompatible.length} tool(s) have compatibility issues. Run \`mcp-lint check\` for details._\n`;
66
+ return [header, separator, ...rows, summary].join('\n') + '\n';
67
+ }
68
+ export function registerCompatCommand(program) {
69
+ program
70
+ .command('compat [input]')
71
+ .description('Show compatibility matrix across all MCP clients')
72
+ .option('--format <format>', 'Output format: terminal|json|markdown', 'terminal')
73
+ .option('--no-color', 'Disable terminal colors')
74
+ .option('--config <path>', 'Path to .mcplintrc.json config file')
75
+ .option('--server <type>', 'Server transport: stdio|sse')
76
+ .option('--url <url>', 'SSE server URL (use with --server sse)')
77
+ .allowExcessArguments(true)
78
+ .action(async (input, options) => {
79
+ try {
80
+ const fileConfig = await loadConfig(options.config);
81
+ const config = {
82
+ clients: DEFAULT_CONFIG.clients,
83
+ ...fileConfig,
84
+ };
85
+ let tools;
86
+ if (options.server === 'stdio') {
87
+ const rawArgv = process.argv;
88
+ const sepIdx = rawArgv.indexOf('--');
89
+ if (sepIdx === -1 || sepIdx >= rawArgv.length - 1) {
90
+ throw new Error('--server stdio requires a command after --');
91
+ }
92
+ const [cmd, ...cmdArgs] = rawArgv.slice(sepIdx + 1);
93
+ tools = await loadStdio(cmd, cmdArgs);
94
+ }
95
+ else if (options.server === 'sse') {
96
+ if (!options.url)
97
+ throw new Error('--server sse requires --url <url>');
98
+ tools = await loadSSE(options.url);
99
+ }
100
+ else {
101
+ if (!input)
102
+ throw new Error('Specify an input file or use --server stdio|sse');
103
+ tools = await loadFile(input);
104
+ }
105
+ const engine = new LintEngine(allRules, config);
106
+ const diagnostics = engine.lint(tools);
107
+ const matrix = buildCompatMatrix(tools, diagnostics);
108
+ let output;
109
+ if (options.format === 'json') {
110
+ output = formatCompatJson(matrix);
111
+ }
112
+ else if (options.format === 'markdown') {
113
+ output = formatCompatMarkdown(matrix);
114
+ }
115
+ else {
116
+ output = formatCompatTerminal(matrix, !options.color);
117
+ }
118
+ process.stdout.write(output);
119
+ const hasIncompatible = matrix.tools.some((t) => matrix.clients.some((c) => !matrix.cells[t][c].compatible));
120
+ process.exit(hasIncompatible ? 1 : 0);
121
+ }
122
+ catch (err) {
123
+ process.stderr.write(`Error: ${err.message}\n`);
124
+ process.exit(2);
125
+ }
126
+ });
127
+ }
128
+ //# sourceMappingURL=compat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compat.js","sourceRoot":"","sources":["../../../src/cli/commands/compat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerExplainCommand(program: Command): void;
@@ -0,0 +1,50 @@
1
+ import { allRules } from '../../rules/index.js';
2
+ import chalk from 'chalk';
3
+ export function registerExplainCommand(program) {
4
+ program
5
+ .command('explain <rule-id>')
6
+ .description('Show documentation and examples for a rule')
7
+ .option('--no-color', 'Disable terminal colors')
8
+ .action((ruleId, options) => {
9
+ if (!options.color)
10
+ chalk.level = 0;
11
+ const rule = allRules.find((r) => r.id === ruleId);
12
+ if (!rule) {
13
+ const available = allRules.map((r) => ` ${r.id}`).join('\n');
14
+ process.stderr.write(`Unknown rule "${ruleId}".\n\nAvailable rules:\n${available}\n`);
15
+ process.exit(2);
16
+ }
17
+ const lines = [];
18
+ lines.push('');
19
+ lines.push(chalk.bold(`Rule: ${chalk.cyan(rule.id)}`));
20
+ lines.push(`Severity: ${rule.severity === 'error' ? chalk.red(rule.severity) : chalk.yellow(rule.severity)}`);
21
+ lines.push(`Clients: ${rule.clients.join(', ')}`);
22
+ lines.push(`Fixable: ${rule.docs?.fixNote ? chalk.green('yes') : chalk.dim('no')}`);
23
+ lines.push('');
24
+ lines.push(chalk.bold('Description'));
25
+ lines.push(rule.description);
26
+ if (rule.docs) {
27
+ lines.push('');
28
+ lines.push(chalk.bold('Why'));
29
+ lines.push(rule.docs.why);
30
+ lines.push('');
31
+ lines.push(chalk.bold('Bad example'));
32
+ lines.push(JSON.stringify(rule.docs.badExample, null, 2));
33
+ lines.push('');
34
+ lines.push(chalk.bold('Good example'));
35
+ lines.push(JSON.stringify(rule.docs.goodExample, null, 2));
36
+ if (rule.docs.fixNote) {
37
+ lines.push('');
38
+ lines.push(chalk.bold('Auto-fix'));
39
+ lines.push(rule.docs.fixNote);
40
+ }
41
+ }
42
+ else {
43
+ lines.push('');
44
+ lines.push(chalk.dim('No additional documentation available for this rule.'));
45
+ }
46
+ lines.push('');
47
+ process.stdout.write(lines.join('\n'));
48
+ });
49
+ }
50
+ //# sourceMappingURL=explain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.js","sourceRoot":"","sources":["../../../src/cli/commands/explain.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  import type { Config } from '../core/rule.js';
2
2
  export declare function loadConfig(configPath?: string): Promise<Config>;
3
- export declare const DEFAULT_CONFIG: Required<Omit<Config, 'rules'>>;
3
+ export declare const DEFAULT_CONFIG: Required<Omit<Config, 'rules' | 'extends'>>;
@@ -1,6 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { resolve, dirname } from 'node:path';
3
3
  import { homedir } from 'node:os';
4
+ import { applyExtends } from './presets.js';
4
5
  async function readConfigFile(filePath) {
5
6
  const content = await readFile(filePath, 'utf-8');
6
7
  return JSON.parse(content);
@@ -8,7 +9,8 @@ async function readConfigFile(filePath) {
8
9
  export async function loadConfig(configPath) {
9
10
  if (configPath) {
10
11
  try {
11
- return await readConfigFile(configPath);
12
+ const raw = await readConfigFile(configPath);
13
+ return applyExtends(raw);
12
14
  }
13
15
  catch (err) {
14
16
  throw new Error(`Cannot load config from "${configPath}": ${err.message}`);
@@ -18,7 +20,8 @@ export async function loadConfig(configPath) {
18
20
  const home = homedir();
19
21
  while (true) {
20
22
  try {
21
- return await readConfigFile(resolve(dir, '.mcplintrc.json'));
23
+ const raw = await readConfigFile(resolve(dir, '.mcplintrc.json'));
24
+ return applyExtends(raw);
22
25
  }
23
26
  catch {
24
27
  // not found here, try parent
@@ -31,7 +34,7 @@ export async function loadConfig(configPath) {
31
34
  return {};
32
35
  }
33
36
  export const DEFAULT_CONFIG = {
34
- clients: ['claude', 'cursor', 'gemini', 'vscode'],
37
+ clients: ['claude', 'cursor', 'gemini', 'vscode', 'windsurf', 'cline', 'openai', 'continue'],
35
38
  ignore: [],
36
39
  maxDepth: 5,
37
40
  };
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGlC,KAAK,UAAU,cAAc,CAAC,QAAgB;IAC5C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAClD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAW,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAmB;IAClD,IAAI,UAAU,EAAE,CAAC;QACf,IAAI,CAAC;YACH,OAAO,MAAM,cAAc,CAAC,UAAU,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,4BAA4B,UAAU,MAAO,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;IAED,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IACxB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IAEvB,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,OAAO,MAAM,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACP,6BAA6B;QAC/B,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,KAAK,GAAG,IAAI,GAAG,KAAK,IAAI;YAAE,MAAM;QAC1C,GAAG,GAAG,MAAM,CAAC;IACf,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAoC;IAC7D,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;IACjD,MAAM,EAAE,EAAE;IACV,QAAQ,EAAE,CAAC;CACZ,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,KAAK,UAAU,cAAc,CAAC,QAAgB;IAC5C,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAClD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAW,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAmB;IAClD,IAAI,UAAU,EAAE,CAAC;QACf,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,cAAc,CAAC,UAAU,CAAC,CAAC;YAC7C,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,4BAA4B,UAAU,MAAO,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;IAED,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IACxB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IAEvB,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC,CAAC;YAClE,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,6BAA6B;QAC/B,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,KAAK,GAAG,IAAI,GAAG,KAAK,IAAI;YAAE,MAAM;QAC1C,GAAG,GAAG,MAAM,CAAC;IACf,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAgD;IACzE,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC;IAC5F,MAAM,EAAE,EAAE;IACV,QAAQ,EAAE,CAAC;CACZ,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Config } from '../core/rule.js';
2
+ export declare function applyExtends(userConfig: Config): Config;
@@ -0,0 +1,49 @@
1
+ const recommended = {
2
+ clients: ['claude', 'cursor', 'gemini', 'vscode', 'windsurf', 'cline', 'openai', 'continue'],
3
+ ignore: [],
4
+ maxDepth: 5,
5
+ };
6
+ const strict = {
7
+ clients: ['claude', 'cursor', 'gemini', 'vscode', 'windsurf', 'cline', 'openai', 'continue'],
8
+ ignore: [],
9
+ maxDepth: 3,
10
+ rules: {
11
+ 'no-required-false': 'error',
12
+ 'no-content-encoding': 'error',
13
+ 'description-exists': 'error',
14
+ 'no-empty-enum': 'error',
15
+ 'max-depth': 'error',
16
+ 'no-recursive-refs': 'error',
17
+ 'valid-json-schema-subset': 'error',
18
+ 'no-unsupported-formats': 'error',
19
+ 'claude/no-type-array': 'error',
20
+ 'cursor/no-default-without-type': 'error',
21
+ 'gemini/no-optional-without-default': 'error',
22
+ 'gemini/no-nested-objects': 'error',
23
+ 'vscode/max-params': 'error',
24
+ 'windsurf/no-union-types': 'error',
25
+ 'cline/description-max-length': 'error',
26
+ 'openai/no-additional-properties': 'error',
27
+ 'openai/strict-types': 'error',
28
+ 'continue/no-default-values': 'error',
29
+ },
30
+ };
31
+ const PRESETS = { recommended, strict };
32
+ export function applyExtends(userConfig) {
33
+ const presetName = userConfig.extends;
34
+ if (!presetName)
35
+ return userConfig;
36
+ const preset = PRESETS[presetName];
37
+ if (!preset) {
38
+ throw new Error(`Unknown preset "${presetName}". Available: ${Object.keys(PRESETS).join(', ')}`);
39
+ }
40
+ const { extends: _removed, ...restUser } = userConfig;
41
+ return {
42
+ ...preset,
43
+ ...restUser,
44
+ rules: { ...preset.rules, ...restUser.rules },
45
+ clients: restUser.clients ?? preset.clients,
46
+ ignore: [...(preset.ignore ?? []), ...(restUser.ignore ?? [])],
47
+ };
48
+ }
49
+ //# sourceMappingURL=presets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presets.js","sourceRoot":"","sources":["../../src/config/presets.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,GAAW;IAC1B,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC;IAC5F,MAAM,EAAE,EAAE;IACV,QAAQ,EAAE,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,GAAW;IACrB,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC;IAC5F,MAAM,EAAE,EAAE;IACV,QAAQ,EAAE,CAAC;IACX,KAAK,EAAE;QACL,mBAAmB,EAAE,OAAO;QAC5B,qBAAqB,EAAE,OAAO;QAC9B,oBAAoB,EAAE,OAAO;QAC7B,eAAe,EAAE,OAAO;QACxB,WAAW,EAAE,OAAO;QACpB,mBAAmB,EAAE,OAAO;QAC5B,0BAA0B,EAAE,OAAO;QACnC,wBAAwB,EAAE,OAAO;QACjC,sBAAsB,EAAE,OAAO;QAC/B,gCAAgC,EAAE,OAAO;QACzC,oCAAoC,EAAE,OAAO;QAC7C,0BAA0B,EAAE,OAAO;QACnC,mBAAmB,EAAE,OAAO;QAC5B,yBAAyB,EAAE,OAAO;QAClC,8BAA8B,EAAE,OAAO;QACvC,iCAAiC,EAAE,OAAO;QAC1C,qBAAqB,EAAE,OAAO;QAC9B,4BAA4B,EAAE,OAAO;KACtC;CACF,CAAC;AAEF,MAAM,OAAO,GAA2B,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;AAEhE,MAAM,UAAU,YAAY,CAAC,UAAkB;IAC7C,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC;IACtC,IAAI,CAAC,UAAU;QAAE,OAAO,UAAU,CAAC;IAEnC,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACnC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,mBAAmB,UAAU,iBAAiB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnG,CAAC;IAED,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,QAAQ,EAAE,GAAG,UAAU,CAAC;IACtD,OAAO;QACL,GAAG,MAAM;QACT,GAAG,QAAQ;QACX,KAAK,EAAE,EAAE,GAAG,MAAM,CAAC,KAAK,EAAE,GAAG,QAAQ,CAAC,KAAK,EAAE;QAC7C,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO;QAC3C,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;KAC/D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { ClientId, MCPTool } from './rule.js';
2
+ import type { Diagnostic } from './diagnostic.js';
3
+ export interface CompatCell {
4
+ compatible: boolean;
5
+ errorCount: number;
6
+ issues: Diagnostic[];
7
+ }
8
+ export interface CompatMatrix {
9
+ tools: string[];
10
+ clients: ClientId[];
11
+ cells: Record<string, Record<ClientId, CompatCell>>;
12
+ }
13
+ export declare function buildCompatMatrix(tools: MCPTool[], diagnostics: Diagnostic[]): CompatMatrix;
@@ -0,0 +1,31 @@
1
+ const ALL_CLIENTS = [
2
+ 'claude', 'cursor', 'gemini', 'vscode',
3
+ 'windsurf', 'cline', 'openai', 'continue',
4
+ ];
5
+ export function buildCompatMatrix(tools, diagnostics) {
6
+ const cells = {};
7
+ for (const tool of tools) {
8
+ cells[tool.name] = {};
9
+ for (const client of ALL_CLIENTS) {
10
+ cells[tool.name][client] = { compatible: true, errorCount: 0, issues: [] };
11
+ }
12
+ }
13
+ for (const diag of diagnostics) {
14
+ for (const client of diag.clients) {
15
+ const cell = cells[diag.toolName]?.[client];
16
+ if (!cell)
17
+ continue;
18
+ cell.issues.push(diag);
19
+ if (diag.severity === 'error') {
20
+ cell.errorCount++;
21
+ cell.compatible = false;
22
+ }
23
+ }
24
+ }
25
+ return {
26
+ tools: tools.map((t) => t.name),
27
+ clients: ALL_CLIENTS,
28
+ cells,
29
+ };
30
+ }
31
+ //# sourceMappingURL=compat-matrix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compat-matrix.js","sourceRoot":"","sources":["../../src/core/compat-matrix.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,GAAe;IAC9B,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ;IACtC,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU;CAC1C,CAAC;AAEF,MAAM,UAAU,iBAAiB,CAAC,KAAgB,EAAE,WAAyB;IAC3E,MAAM,KAAK,GAAiD,EAAE,CAAC;IAE/D,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAkC,CAAC;QACtD,KAAK,MAAM,MAAM,IAAI,WAAW,EAAE,CAAC;YACjC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;QAC7E,CAAC;IACH,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;YAC5C,IAAI,CAAC,IAAI;gBAAE,SAAS;YACpB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvB,IAAI,IAAI,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;gBAC9B,IAAI,CAAC,UAAU,EAAE,CAAC;gBAClB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/B,OAAO,EAAE,WAAW;QACpB,KAAK;KACN,CAAC;AACJ,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import type { Severity } from './severity.js';
2
2
  export type { Severity } from './severity.js';
3
3
  import type { Diagnostic } from './diagnostic.js';
4
- export type ClientId = 'claude' | 'cursor' | 'gemini' | 'vscode';
4
+ export type ClientId = 'claude' | 'cursor' | 'gemini' | 'vscode' | 'windsurf' | 'cline' | 'openai' | 'continue';
5
5
  export interface JSONSchema {
6
6
  type?: string | string[];
7
7
  properties?: Record<string, JSONSchema>;
@@ -20,6 +20,7 @@ export interface JSONSchema {
20
20
  not?: JSONSchema;
21
21
  patternProperties?: Record<string, JSONSchema>;
22
22
  additionalItems?: JSONSchema | boolean;
23
+ additionalProperties?: JSONSchema | boolean;
23
24
  dependencies?: Record<string, JSONSchema | string[]>;
24
25
  $dynamicRef?: string;
25
26
  contentEncoding?: string;
@@ -34,6 +35,7 @@ export interface MCPTool {
34
35
  inputSchema: JSONSchema;
35
36
  }
36
37
  export interface Config {
38
+ extends?: string;
37
39
  rules?: Record<string, Severity | 'off'>;
38
40
  clients?: ClientId[];
39
41
  ignore?: string[];
@@ -42,11 +44,18 @@ export interface Config {
42
44
  export interface RuleContext {
43
45
  config: Config;
44
46
  }
47
+ export interface RuleDocs {
48
+ why: string;
49
+ badExample: object;
50
+ goodExample: object;
51
+ fixNote?: string;
52
+ }
45
53
  export interface Rule {
46
54
  id: string;
47
55
  severity: Severity;
48
56
  description: string;
49
57
  url?: string;
50
58
  clients: ClientId[];
59
+ docs?: RuleDocs;
51
60
  check: (tool: MCPTool, context: RuleContext) => Diagnostic[];
52
61
  }
package/dist/index.js CHANGED
@@ -3,13 +3,17 @@ import { Command } from 'commander';
3
3
  import { registerCheckCommand } from './cli/commands/check.js';
4
4
  import { registerFixCommand } from './cli/commands/fix.js';
5
5
  import { registerInitCommand } from './cli/commands/init.js';
6
+ import { registerCompatCommand } from './cli/commands/compat.js';
7
+ import { registerExplainCommand } from './cli/commands/explain.js';
6
8
  const program = new Command();
7
9
  program
8
10
  .name('mcp-lint')
9
11
  .description('Lint MCP server tool schemas for cross-client compatibility')
10
- .version('0.1.0');
12
+ .version('0.2.0');
11
13
  registerCheckCommand(program);
12
14
  registerFixCommand(program);
13
15
  registerInitCommand(program);
16
+ registerCompatCommand(program);
17
+ registerExplainCommand(program);
14
18
  program.parse();
15
19
  //# 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;AAE7D,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;AAE7B,OAAO,CAAC,KAAK,EAAE,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;AAEnE,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;AAEhC,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from '../../core/rule.js';
2
+ export declare const clineDescriptionMaxLength: Rule;
@@ -0,0 +1,49 @@
1
+ const MAX_LENGTH = 200;
2
+ export const clineDescriptionMaxLength = {
3
+ id: 'cline/description-max-length',
4
+ severity: 'warning',
5
+ description: `Cline truncates descriptions longer than ${MAX_LENGTH} characters.`,
6
+ clients: ['cline'],
7
+ docs: {
8
+ why: 'Cline\'s UI and prompt construction truncates tool and parameter descriptions at 200 characters. Longer descriptions are silently cut off, hiding important information.',
9
+ badExample: { description: 'A very long description that exceeds two hundred characters and will be truncated by Cline without any warning, potentially causing confusion for users who rely on the full text.' },
10
+ goodExample: { description: 'A concise description under 200 characters.' },
11
+ fixNote: 'Trim description to 200 characters.',
12
+ },
13
+ check(tool, _context) {
14
+ const diagnostics = [];
15
+ if (tool.description && tool.description.length > MAX_LENGTH) {
16
+ diagnostics.push({
17
+ ruleId: 'cline/description-max-length',
18
+ severity: 'warning',
19
+ message: `Tool "${tool.name}" description is ${tool.description.length} characters. Cline truncates at ${MAX_LENGTH}.`,
20
+ toolName: tool.name,
21
+ path: 'description',
22
+ clients: ['cline'],
23
+ fix: {
24
+ description: `Trim description to ${MAX_LENGTH} characters`,
25
+ apply: (s) => ({ ...s, description: s.description.slice(0, MAX_LENGTH) }),
26
+ },
27
+ });
28
+ }
29
+ const props = tool.inputSchema.properties ?? {};
30
+ for (const [propName, propSchema] of Object.entries(props)) {
31
+ if (typeof propSchema.description === 'string' && propSchema.description.length > MAX_LENGTH) {
32
+ diagnostics.push({
33
+ ruleId: 'cline/description-max-length',
34
+ severity: 'warning',
35
+ message: `Parameter "${propName}" description is ${propSchema.description.length} characters. Cline truncates at ${MAX_LENGTH}.`,
36
+ toolName: tool.name,
37
+ path: `inputSchema.properties.${propName}`,
38
+ clients: ['cline'],
39
+ fix: {
40
+ description: `Trim description to ${MAX_LENGTH} characters`,
41
+ apply: (s) => ({ ...s, description: s.description.slice(0, MAX_LENGTH) }),
42
+ },
43
+ });
44
+ }
45
+ }
46
+ return diagnostics;
47
+ },
48
+ };
49
+ //# sourceMappingURL=cline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cline.js","sourceRoot":"","sources":["../../../src/rules/client-specific/cline.ts"],"names":[],"mappings":"AAIA,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB,MAAM,CAAC,MAAM,yBAAyB,GAAS;IAC7C,EAAE,EAAE,8BAA8B;IAClC,QAAQ,EAAE,SAAS;IACnB,WAAW,EAAE,4CAA4C,UAAU,cAAc;IACjF,OAAO,EAAE,CAAC,OAAO,CAAC;IAClB,IAAI,EAAE;QACJ,GAAG,EAAE,0KAA0K;QAC/K,UAAU,EAAE,EAAE,WAAW,EAAE,oLAAoL,EAAE;QACjN,WAAW,EAAE,EAAE,WAAW,EAAE,6CAA6C,EAAE;QAC3E,OAAO,EAAE,qCAAqC;KAC/C;IAED,KAAK,CAAC,IAAa,EAAE,QAAqB;QACxC,MAAM,WAAW,GAAiB,EAAE,CAAC;QAErC,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;YAC7D,WAAW,CAAC,IAAI,CAAC;gBACf,MAAM,EAAE,8BAA8B;gBACtC,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,SAAS,IAAI,CAAC,IAAI,oBAAoB,IAAI,CAAC,WAAW,CAAC,MAAM,mCAAmC,UAAU,GAAG;gBACtH,QAAQ,EAAE,IAAI,CAAC,IAAI;gBACnB,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,CAAC,OAAO,CAAC;gBAClB,GAAG,EAAE;oBACH,WAAW,EAAE,uBAAuB,UAAU,aAAa;oBAC3D,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,WAAW,EAAG,CAAC,CAAC,WAAsB,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC;iBACtF;aACF,CAAC,CAAC;QACL,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,UAAU,IAAI,EAAE,CAAC;QAChD,KAAK,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3D,IAAI,OAAO,UAAU,CAAC,WAAW,KAAK,QAAQ,IAAI,UAAU,CAAC,WAAW,CAAC,MAAM,GAAG,UAAU,EAAE,CAAC;gBAC7F,WAAW,CAAC,IAAI,CAAC;oBACf,MAAM,EAAE,8BAA8B;oBACtC,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,cAAc,QAAQ,oBAAoB,UAAU,CAAC,WAAW,CAAC,MAAM,mCAAmC,UAAU,GAAG;oBAChI,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,IAAI,EAAE,0BAA0B,QAAQ,EAAE;oBAC1C,OAAO,EAAE,CAAC,OAAO,CAAC;oBAClB,GAAG,EAAE;wBACH,WAAW,EAAE,uBAAuB,UAAU,aAAa;wBAC3D,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,WAAW,EAAG,CAAC,CAAC,WAAsB,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC;qBACtF;iBACF,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,WAAW,CAAC;IACrB,CAAC;CACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from '../../core/rule.js';
2
+ export declare const continueNoDefaultValues: Rule;
@@ -0,0 +1,30 @@
1
+ export const continueNoDefaultValues = {
2
+ id: 'continue/no-default-values',
3
+ severity: 'warning',
4
+ description: 'Continue.dev ignores `default` fields — users may assume defaults are applied when they are not.',
5
+ clients: ['continue'],
6
+ docs: {
7
+ why: 'Continue.dev\'s MCP client does not apply `default` values from schemas. If a user omits an optional parameter, they won\'t get the default — they\'ll get an error or undefined behavior.',
8
+ badExample: { properties: { limit: { type: 'number', default: 10 } } },
9
+ goodExample: { properties: { limit: { type: 'number', description: 'Number of results (optional, defaults to 10 if omitted)' } } },
10
+ fixNote: 'Document the default value in the description instead of relying on the `default` field.',
11
+ },
12
+ check(tool, _context) {
13
+ const diagnostics = [];
14
+ const props = tool.inputSchema.properties ?? {};
15
+ for (const [propName, propSchema] of Object.entries(props)) {
16
+ if (propSchema.default !== undefined) {
17
+ diagnostics.push({
18
+ ruleId: 'continue/no-default-values',
19
+ severity: 'warning',
20
+ message: `Parameter "${propName}" in tool "${tool.name}" has a \`default\` value that Continue.dev will ignore. Document the default in the description instead.`,
21
+ toolName: tool.name,
22
+ path: `inputSchema.properties.${propName}`,
23
+ clients: ['continue'],
24
+ });
25
+ }
26
+ }
27
+ return diagnostics;
28
+ },
29
+ };
30
+ //# sourceMappingURL=continue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"continue.js","sourceRoot":"","sources":["../../../src/rules/client-specific/continue.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,uBAAuB,GAAS;IAC3C,EAAE,EAAE,4BAA4B;IAChC,QAAQ,EAAE,SAAS;IACnB,WAAW,EAAE,kGAAkG;IAC/G,OAAO,EAAE,CAAC,UAAU,CAAC;IACrB,IAAI,EAAE;QACJ,GAAG,EAAE,4LAA4L;QACjM,UAAU,EAAE,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE;QACtE,WAAW,EAAE,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,yDAAyD,EAAE,EAAE,EAAE;QAClI,OAAO,EAAE,0FAA0F;KACpG;IAED,KAAK,CAAC,IAAa,EAAE,QAAqB;QACxC,MAAM,WAAW,GAAiB,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,UAAU,IAAI,EAAE,CAAC;QAEhD,KAAK,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3D,IAAI,UAAU,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;gBACrC,WAAW,CAAC,IAAI,CAAC;oBACf,MAAM,EAAE,4BAA4B;oBACpC,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,cAAc,QAAQ,cAAc,IAAI,CAAC,IAAI,2GAA2G;oBACjK,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,IAAI,EAAE,0BAA0B,QAAQ,EAAE;oBAC1C,OAAO,EAAE,CAAC,UAAU,CAAC;iBACtB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,WAAW,CAAC;IACrB,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Rule } from '../../core/rule.js';
2
+ export declare const openaiNoAdditionalProperties: Rule;
3
+ export declare const openaiStrictTypes: Rule;
@@ -0,0 +1,59 @@
1
+ import { walkSchema } from '../../utils/schema-walker.js';
2
+ const ALLOWED_TYPES = new Set(['string', 'number', 'boolean', 'object', 'array', 'null']);
3
+ export const openaiNoAdditionalProperties = {
4
+ id: 'openai/no-additional-properties',
5
+ severity: 'error',
6
+ description: 'OpenAI Agents SDK strict mode requires `additionalProperties: false` on the root inputSchema.',
7
+ clients: ['openai'],
8
+ docs: {
9
+ why: 'OpenAI\'s strict function calling mode rejects schemas that don\'t explicitly set `additionalProperties: false` at the root level.',
10
+ badExample: { type: 'object', properties: { q: { type: 'string' } } },
11
+ goodExample: { type: 'object', properties: { q: { type: 'string' } }, additionalProperties: false },
12
+ fixNote: 'Add `"additionalProperties": false` to the root inputSchema.',
13
+ },
14
+ check(tool, _context) {
15
+ if (tool.inputSchema.additionalProperties === false)
16
+ return [];
17
+ return [{
18
+ ruleId: 'openai/no-additional-properties',
19
+ severity: 'error',
20
+ message: `Tool "${tool.name}" is missing \`additionalProperties: false\` on inputSchema. Required for OpenAI Agents SDK strict mode.`,
21
+ toolName: tool.name,
22
+ path: 'inputSchema',
23
+ clients: ['openai'],
24
+ fix: {
25
+ description: 'Add `additionalProperties: false` to inputSchema',
26
+ apply: (s) => ({ ...s, additionalProperties: false }),
27
+ },
28
+ }];
29
+ },
30
+ };
31
+ export const openaiStrictTypes = {
32
+ id: 'openai/strict-types',
33
+ severity: 'error',
34
+ description: 'OpenAI Agents SDK only allows string, number, boolean, object, array, null types.',
35
+ clients: ['openai'],
36
+ docs: {
37
+ why: 'OpenAI\'s function calling schema validation rejects non-standard types like `integer`. Use `number` instead.',
38
+ badExample: { properties: { count: { type: 'integer' } } },
39
+ goodExample: { properties: { count: { type: 'number' } } },
40
+ },
41
+ check(tool, _context) {
42
+ const diagnostics = [];
43
+ walkSchema(tool.inputSchema, (schema, path) => {
44
+ if (typeof schema.type === 'string' && !ALLOWED_TYPES.has(schema.type)) {
45
+ const diagnosticPath = path ? `inputSchema.${path}` : 'inputSchema';
46
+ diagnostics.push({
47
+ ruleId: 'openai/strict-types',
48
+ severity: 'error',
49
+ message: `Schema at "${diagnosticPath}" uses type "${schema.type}" which is not allowed by OpenAI Agents SDK. Use one of: ${[...ALLOWED_TYPES].join(', ')}.`,
50
+ toolName: tool.name,
51
+ path: diagnosticPath,
52
+ clients: ['openai'],
53
+ });
54
+ }
55
+ });
56
+ return diagnostics;
57
+ },
58
+ };
59
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../src/rules/client-specific/openai.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from '../../core/rule.js';
2
+ export declare const windsurfNoUnionTypes: Rule;
@@ -0,0 +1,39 @@
1
+ import { walkSchema } from '../../utils/schema-walker.js';
2
+ export const windsurfNoUnionTypes = {
3
+ id: 'windsurf/no-union-types',
4
+ severity: 'warning',
5
+ description: 'Windsurf does not support `anyOf` with more than 2 variants.',
6
+ clients: ['windsurf'],
7
+ docs: {
8
+ why: 'Windsurf\'s MCP client has limited support for complex union types. `anyOf` with more than 2 variants may be silently ignored or cause unexpected behavior.',
9
+ badExample: {
10
+ properties: {
11
+ value: { anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }] },
12
+ },
13
+ },
14
+ goodExample: {
15
+ properties: {
16
+ value: { anyOf: [{ type: 'string' }, { type: 'null' }] },
17
+ },
18
+ },
19
+ fixNote: 'Reduce anyOf to at most 2 variants, or replace with a single type.',
20
+ },
21
+ check(tool, _context) {
22
+ const diagnostics = [];
23
+ walkSchema(tool.inputSchema, (schema, path) => {
24
+ if (Array.isArray(schema.anyOf) && schema.anyOf.length > 2) {
25
+ const diagnosticPath = path ? `inputSchema.${path}` : 'inputSchema';
26
+ diagnostics.push({
27
+ ruleId: 'windsurf/no-union-types',
28
+ severity: 'warning',
29
+ message: `Schema at "${diagnosticPath}" uses \`anyOf\` with ${schema.anyOf.length} variants. Windsurf supports at most 2.`,
30
+ toolName: tool.name,
31
+ path: diagnosticPath,
32
+ clients: ['windsurf'],
33
+ });
34
+ }
35
+ });
36
+ return diagnostics;
37
+ },
38
+ };
39
+ //# sourceMappingURL=windsurf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"windsurf.js","sourceRoot":"","sources":["../../../src/rules/client-specific/windsurf.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE1D,MAAM,CAAC,MAAM,oBAAoB,GAAS;IACxC,EAAE,EAAE,yBAAyB;IAC7B,QAAQ,EAAE,SAAS;IACnB,WAAW,EAAE,8DAA8D;IAC3E,OAAO,EAAE,CAAC,UAAU,CAAC;IACrB,IAAI,EAAE;QACJ,GAAG,EAAE,6JAA6J;QAClK,UAAU,EAAE;YACV,UAAU,EAAE;gBACV,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE;aAChF;SACF;QACD,WAAW,EAAE;YACX,UAAU,EAAE;gBACV,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE;aACzD;SACF;QACD,OAAO,EAAE,oEAAoE;KAC9E;IAED,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,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,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,yBAAyB;oBACjC,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,cAAc,cAAc,yBAAyB,MAAM,CAAC,KAAK,CAAC,MAAM,yCAAyC;oBAC1H,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,IAAI,EAAE,cAAc;oBACpB,OAAO,EAAE,CAAC,UAAU,CAAC;iBACtB,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,WAAW,CAAC;IACrB,CAAC;CACF,CAAC"}
@@ -7,9 +7,13 @@ import { noRecursiveRefs } from './no-recursive-refs.js';
7
7
  import { validJsonSchemaSubset } from './valid-json-schema-subset.js';
8
8
  import { noUnsupportedFormats } from './no-unsupported-formats.js';
9
9
  import { claudeNoTypeArray } from './client-specific/claude.js';
10
+ import { continueNoDefaultValues } from './client-specific/continue.js';
10
11
  import { cursorNoDefaultWithoutType } from './client-specific/cursor.js';
11
12
  import { geminiNoOptionalWithoutDefault, geminiNoNestedObjects } from './client-specific/gemini.js';
12
13
  import { vscodeMaxParams } from './client-specific/vscode.js';
14
+ import { windsurfNoUnionTypes } from './client-specific/windsurf.js';
15
+ import { clineDescriptionMaxLength } from './client-specific/cline.js';
16
+ import { openaiNoAdditionalProperties, openaiStrictTypes } from './client-specific/openai.js';
13
17
  export const allRules = [
14
18
  noRequiredFalse,
15
19
  noContentEncoding,
@@ -20,9 +24,14 @@ export const allRules = [
20
24
  validJsonSchemaSubset,
21
25
  noUnsupportedFormats,
22
26
  claudeNoTypeArray,
27
+ continueNoDefaultValues,
23
28
  cursorNoDefaultWithoutType,
24
29
  geminiNoOptionalWithoutDefault,
25
30
  geminiNoNestedObjects,
26
31
  vscodeMaxParams,
32
+ windsurfNoUnionTypes,
33
+ clineDescriptionMaxLength,
34
+ openaiNoAdditionalProperties,
35
+ openaiStrictTypes,
27
36
  ];
28
37
  //# sourceMappingURL=index.js.map
@@ -1 +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"}
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,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxE,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;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,4BAA4B,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAE9F,MAAM,CAAC,MAAM,QAAQ,GAAW;IAC9B,eAAe;IACf,iBAAiB;IACjB,iBAAiB;IACjB,WAAW;IACX,QAAQ;IACR,eAAe;IACf,qBAAqB;IACrB,oBAAoB;IACpB,iBAAiB;IACjB,uBAAuB;IACvB,0BAA0B;IAC1B,8BAA8B;IAC9B,qBAAqB;IACrB,eAAe;IACf,oBAAoB;IACpB,yBAAyB;IACzB,4BAA4B;IAC5B,iBAAiB;CAClB,CAAC"}
package/package.json CHANGED
@@ -1,44 +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
- }
1
+ {
2
+ "name": "mcp-lint",
3
+ "version": "0.2.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": ">=20"
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
+ }