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,3 @@
1
+ import type { Config } from '../core/rule.js';
2
+ export declare function loadConfig(configPath?: string): Promise<Config>;
3
+ export declare const DEFAULT_CONFIG: Required<Omit<Config, 'rules'>>;
@@ -0,0 +1,38 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { resolve, dirname } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ async function readConfigFile(filePath) {
5
+ const content = await readFile(filePath, 'utf-8');
6
+ return JSON.parse(content);
7
+ }
8
+ export async function loadConfig(configPath) {
9
+ if (configPath) {
10
+ try {
11
+ return await readConfigFile(configPath);
12
+ }
13
+ catch (err) {
14
+ throw new Error(`Cannot load config from "${configPath}": ${err.message}`);
15
+ }
16
+ }
17
+ let dir = process.cwd();
18
+ const home = homedir();
19
+ while (true) {
20
+ try {
21
+ return await readConfigFile(resolve(dir, '.mcplintrc.json'));
22
+ }
23
+ catch {
24
+ // not found here, try parent
25
+ }
26
+ const parent = dirname(dir);
27
+ if (parent === dir || dir === home)
28
+ break;
29
+ dir = parent;
30
+ }
31
+ return {};
32
+ }
33
+ export const DEFAULT_CONFIG = {
34
+ clients: ['claude', 'cursor', 'gemini', 'vscode'],
35
+ ignore: [],
36
+ maxDepth: 5,
37
+ };
38
+ //# sourceMappingURL=config.js.map
@@ -0,0 +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"}
@@ -0,0 +1,15 @@
1
+ import type { Severity } from './severity.js';
2
+ import type { ClientId } from './rule.js';
3
+ export interface Fix {
4
+ description: string;
5
+ apply: (schema: Record<string, unknown>) => Record<string, unknown>;
6
+ }
7
+ export interface Diagnostic {
8
+ ruleId: string;
9
+ severity: Severity;
10
+ message: string;
11
+ toolName: string;
12
+ path: string;
13
+ clients: ClientId[];
14
+ fix?: Fix;
15
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=diagnostic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostic.js","sourceRoot":"","sources":["../../src/core/diagnostic.ts"],"names":[],"mappings":""}
@@ -0,0 +1,8 @@
1
+ import type { Rule, MCPTool, Config } from './rule.js';
2
+ import type { Diagnostic } from './diagnostic.js';
3
+ export declare class LintEngine {
4
+ private readonly rules;
5
+ private readonly config;
6
+ constructor(rules: Rule[], config: Config);
7
+ lint(tools: MCPTool[]): Diagnostic[];
8
+ }
@@ -0,0 +1,35 @@
1
+ export class LintEngine {
2
+ rules;
3
+ config;
4
+ constructor(rules, config) {
5
+ this.rules = rules;
6
+ this.config = config;
7
+ }
8
+ lint(tools) {
9
+ const context = { config: this.config };
10
+ const diagnostics = [];
11
+ for (const tool of tools) {
12
+ if (this.config.ignore?.includes(tool.name))
13
+ continue;
14
+ for (const rule of this.rules) {
15
+ const severityOverride = this.config.rules?.[rule.id];
16
+ if (severityOverride === 'off')
17
+ continue;
18
+ // Filter by clients: skip rule if none of its clients are in config.clients
19
+ if (this.config.clients &&
20
+ this.config.clients.length > 0 &&
21
+ !rule.clients.some((c) => this.config.clients.includes(c))) {
22
+ continue;
23
+ }
24
+ const ruleDiagnostics = rule.check(tool, context);
25
+ for (const diag of ruleDiagnostics) {
26
+ diagnostics.push(severityOverride
27
+ ? { ...diag, severity: severityOverride }
28
+ : diag);
29
+ }
30
+ }
31
+ }
32
+ return diagnostics;
33
+ }
34
+ }
35
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/core/engine.ts"],"names":[],"mappings":"AAIA,MAAM,OAAO,UAAU;IAEF;IACA;IAFnB,YACmB,KAAa,EACb,MAAc;QADd,UAAK,GAAL,KAAK,CAAQ;QACb,WAAM,GAAN,MAAM,CAAQ;IAC9B,CAAC;IAEJ,IAAI,CAAC,KAAgB;QACnB,MAAM,OAAO,GAAgB,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;QACrD,MAAM,WAAW,GAAiB,EAAE,CAAC;QAErC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,SAAS;YAEtD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC9B,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACtD,IAAI,gBAAgB,KAAK,KAAK;oBAAE,SAAS;gBAEzC,4EAA4E;gBAC5E,IACE,IAAI,CAAC,MAAM,CAAC,OAAO;oBACnB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;oBAC9B,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,OAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAC3D,CAAC;oBACD,SAAS;gBACX,CAAC;gBAED,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;gBAElD,KAAK,MAAM,IAAI,IAAI,eAAe,EAAE,CAAC;oBACnC,WAAW,CAAC,IAAI,CACd,gBAAgB;wBACd,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,gBAA4B,EAAE;wBACrD,CAAC,CAAC,IAAI,CACT,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,WAAW,CAAC;IACrB,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ import type { MCPTool } from './rule.js';
2
+ import type { Diagnostic } from './diagnostic.js';
3
+ export declare function applyFixes(tools: MCPTool[], diagnostics: Diagnostic[], onlyRules?: string[]): MCPTool[];
@@ -0,0 +1,37 @@
1
+ function parsePath(path) {
2
+ return path.split('.').flatMap((part) => {
3
+ const m = part.match(/^([^\[]+)\[(\d+)\]$/);
4
+ return m ? [m[1], m[2]] : [part];
5
+ });
6
+ }
7
+ export function applyFixes(tools, diagnostics, onlyRules) {
8
+ const fixable = diagnostics.filter((d) => d.fix != null && (!onlyRules || onlyRules.includes(d.ruleId)));
9
+ if (fixable.length === 0)
10
+ return tools;
11
+ const result = structuredClone(tools);
12
+ for (const diag of fixable) {
13
+ const toolIndex = result.findIndex((t) => t.name === diag.toolName);
14
+ if (toolIndex === -1)
15
+ continue;
16
+ const parts = parsePath(diag.path);
17
+ if (parts.length === 0)
18
+ continue;
19
+ const toolObj = result[toolIndex];
20
+ let parent = toolObj;
21
+ for (let i = 0; i < parts.length - 1; i++) {
22
+ const next = parent[parts[i]];
23
+ if (next == null || typeof next !== 'object') {
24
+ parent = {}; // will be skipped below
25
+ break;
26
+ }
27
+ parent = next;
28
+ }
29
+ const lastKey = parts[parts.length - 1];
30
+ const node = parent[lastKey];
31
+ if (node != null && typeof node === 'object') {
32
+ parent[lastKey] = diag.fix.apply(node);
33
+ }
34
+ }
35
+ return result;
36
+ }
37
+ //# sourceMappingURL=fixer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixer.js","sourceRoot":"","sources":["../../src/core/fixer.ts"],"names":[],"mappings":"AAGA,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QACtC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;QAC5C,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,KAAgB,EAChB,WAAyB,EACzB,SAAoB;IAEpB,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAChC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CACrE,CAAC;IACF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAEvC,MAAM,MAAM,GAAc,eAAe,CAAC,KAAK,CAAC,CAAC;IAEjD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,QAAQ,CAAC,CAAC;QACpE,IAAI,SAAS,KAAK,CAAC,CAAC;YAAE,SAAS;QAE/B,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAEjC,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAuC,CAAC;QAExE,IAAI,MAAM,GAA4B,OAAO,CAAC;QAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9B,IAAI,IAAI,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7C,MAAM,GAAG,EAA6B,CAAC,CAAC,wBAAwB;gBAChE,MAAM;YACR,CAAC;YACD,MAAM,GAAG,IAA+B,CAAC;QAC3C,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7B,IAAI,IAAI,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7C,MAAM,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,GAAI,CAAC,KAAK,CAAC,IAA+B,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { Severity } from './severity.js';
2
+ export type { Severity } from './severity.js';
3
+ import type { Diagnostic } from './diagnostic.js';
4
+ export type ClientId = 'claude' | 'cursor' | 'gemini' | 'vscode';
5
+ export interface JSONSchema {
6
+ type?: string | string[];
7
+ properties?: Record<string, JSONSchema>;
8
+ required?: string[] | boolean;
9
+ items?: JSONSchema | JSONSchema[];
10
+ enum?: unknown[];
11
+ $ref?: string;
12
+ $defs?: Record<string, JSONSchema>;
13
+ definitions?: Record<string, JSONSchema>;
14
+ if?: JSONSchema;
15
+ then?: JSONSchema;
16
+ else?: JSONSchema;
17
+ oneOf?: JSONSchema[];
18
+ anyOf?: JSONSchema[];
19
+ allOf?: JSONSchema[];
20
+ not?: JSONSchema;
21
+ patternProperties?: Record<string, JSONSchema>;
22
+ additionalItems?: JSONSchema | boolean;
23
+ dependencies?: Record<string, JSONSchema | string[]>;
24
+ $dynamicRef?: string;
25
+ contentEncoding?: string;
26
+ format?: string;
27
+ default?: unknown;
28
+ description?: string;
29
+ [key: string]: unknown;
30
+ }
31
+ export interface MCPTool {
32
+ name: string;
33
+ description?: string;
34
+ inputSchema: JSONSchema;
35
+ }
36
+ export interface Config {
37
+ rules?: Record<string, Severity | 'off'>;
38
+ clients?: ClientId[];
39
+ ignore?: string[];
40
+ maxDepth?: number;
41
+ }
42
+ export interface RuleContext {
43
+ config: Config;
44
+ }
45
+ export interface Rule {
46
+ id: string;
47
+ severity: Severity;
48
+ description: string;
49
+ url?: string;
50
+ clients: ClientId[];
51
+ check: (tool: MCPTool, context: RuleContext) => Diagnostic[];
52
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule.js","sourceRoot":"","sources":["../../src/core/rule.ts"],"names":[],"mappings":""}
@@ -0,0 +1 @@
1
+ export type Severity = 'error' | 'warning' | 'info';
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=severity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"severity.js","sourceRoot":"","sources":["../../src/core/severity.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { registerCheckCommand } from './cli/commands/check.js';
4
+ import { registerFixCommand } from './cli/commands/fix.js';
5
+ import { registerInitCommand } from './cli/commands/init.js';
6
+ const program = new Command();
7
+ program
8
+ .name('mcp-lint')
9
+ .description('Lint MCP server tool schemas for cross-client compatibility')
10
+ .version('0.1.0');
11
+ registerCheckCommand(program);
12
+ registerFixCommand(program);
13
+ registerInitCommand(program);
14
+ program.parse();
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"}
@@ -0,0 +1,2 @@
1
+ import type { MCPTool } from '../core/rule.js';
2
+ export declare function loadFile(filePath: string): Promise<MCPTool[]>;
@@ -0,0 +1,30 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { extname } from 'node:path';
3
+ import { parse as parseYaml } from 'yaml';
4
+ export async function loadFile(filePath) {
5
+ let content;
6
+ try {
7
+ content = await readFile(filePath, 'utf-8');
8
+ }
9
+ catch (err) {
10
+ throw new Error(`Cannot read file "${filePath}": ${err.message}`);
11
+ }
12
+ const ext = extname(filePath).toLowerCase();
13
+ let data;
14
+ try {
15
+ if (ext === '.yaml' || ext === '.yml') {
16
+ data = parseYaml(content);
17
+ }
18
+ else {
19
+ data = JSON.parse(content);
20
+ }
21
+ }
22
+ catch (err) {
23
+ throw new Error(`Failed to parse "${filePath}": ${err.message}`);
24
+ }
25
+ if (!Array.isArray(data)) {
26
+ throw new Error(`Expected an array of MCP tools in "${filePath}", got ${typeof data}`);
27
+ }
28
+ return data;
29
+ }
30
+ //# sourceMappingURL=file-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-loader.js","sourceRoot":"","sources":["../../src/loaders/file-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAG1C,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAgB;IAC7C,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,qBAAqB,QAAQ,MAAO,GAA6B,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/F,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,IAAa,CAAC;IAElB,IAAI,CAAC;QACH,IAAI,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;YACtC,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;aAAM,CAAC;YACN,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,oBAAoB,QAAQ,MAAO,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9E,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,sCAAsC,QAAQ,UAAU,OAAO,IAAI,EAAE,CAAC,CAAC;IACzF,CAAC;IAED,OAAO,IAAiB,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { MCPTool } from '../core/rule.js';
2
+ export declare function loadSSE(url: string): Promise<MCPTool[]>;
@@ -0,0 +1,19 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
3
+ export async function loadSSE(url) {
4
+ const transport = new SSEClientTransport(new URL(url));
5
+ const client = new Client({ name: 'mcp-lint', version: '0.1.0' }, { capabilities: {} });
6
+ await client.connect(transport);
7
+ try {
8
+ const result = await client.listTools();
9
+ return result.tools.map((t) => ({
10
+ name: t.name,
11
+ description: t.description,
12
+ inputSchema: t.inputSchema,
13
+ }));
14
+ }
15
+ finally {
16
+ await client.close();
17
+ }
18
+ }
19
+ //# sourceMappingURL=sse-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-loader.js","sourceRoot":"","sources":["../../src/loaders/sse-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAG7E,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,GAAW;IACvC,MAAM,SAAS,GAAG,IAAI,kBAAkB,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;IAExF,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;QACxC,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC9B,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,WAAW,EAAE,CAAC,CAAC,WAAqC;SACrD,CAAC,CAAC,CAAC;IACN,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { MCPTool } from '../core/rule.js';
2
+ export declare function loadStdio(command: string, args: string[]): Promise<MCPTool[]>;
@@ -0,0 +1,19 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
3
+ export async function loadStdio(command, args) {
4
+ const transport = new StdioClientTransport({ command, args });
5
+ const client = new Client({ name: 'mcp-lint', version: '0.1.0' }, { capabilities: {} });
6
+ await client.connect(transport);
7
+ try {
8
+ const result = await client.listTools();
9
+ return result.tools.map((t) => ({
10
+ name: t.name,
11
+ description: t.description,
12
+ inputSchema: t.inputSchema,
13
+ }));
14
+ }
15
+ finally {
16
+ await client.close();
17
+ }
18
+ }
19
+ //# sourceMappingURL=stdio-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio-loader.js","sourceRoot":"","sources":["../../src/loaders/stdio-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAGjF,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAe,EAAE,IAAc;IAC7D,MAAM,SAAS,GAAG,IAAI,oBAAoB,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;IAExF,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;QACxC,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC9B,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,WAAW,EAAE,CAAC,CAAC,WAAqC;SACrD,CAAC,CAAC,CAAC;IACN,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from '../../core/rule.js';
2
+ export declare const claudeNoTypeArray: Rule;
@@ -0,0 +1,30 @@
1
+ import { walkSchema } from '../../utils/schema-walker.js';
2
+ export const claudeNoTypeArray = {
3
+ id: 'claude/no-type-array',
4
+ severity: 'warning',
5
+ description: 'Claude may not handle array syntax for `type` (e.g. `["string", "null"]`) correctly.',
6
+ clients: ['claude'],
7
+ check(tool, _context) {
8
+ const diagnostics = [];
9
+ walkSchema(tool.inputSchema, (schema, path) => {
10
+ if (Array.isArray(schema.type)) {
11
+ const diagnosticPath = path ? `inputSchema.${path}` : 'inputSchema';
12
+ const primaryType = schema.type.find((t) => t !== 'null') ?? schema.type[0];
13
+ diagnostics.push({
14
+ ruleId: 'claude/no-type-array',
15
+ severity: 'warning',
16
+ message: `Array type syntax \`"type": ${JSON.stringify(schema.type)}\` at "${diagnosticPath}" may not be handled correctly by Claude. Use a single type string.`,
17
+ toolName: tool.name,
18
+ path: diagnosticPath,
19
+ clients: ['claude'],
20
+ fix: {
21
+ description: `Convert to single type "${primaryType}"`,
22
+ apply: (s) => ({ ...s, type: primaryType }),
23
+ },
24
+ });
25
+ }
26
+ });
27
+ return diagnostics;
28
+ },
29
+ };
30
+ //# sourceMappingURL=claude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../../../src/rules/client-specific/claude.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE1D,MAAM,CAAC,MAAM,iBAAiB,GAAS;IACrC,EAAE,EAAE,sBAAsB;IAC1B,QAAQ,EAAE,SAAS;IACnB,WAAW,EAAE,sFAAsF;IACnG,OAAO,EAAE,CAAC,QAAQ,CAAC;IAEnB,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,EAAE,CAAC;gBAC/B,MAAM,cAAc,GAAG,IAAI,CAAC,CAAC,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;gBACpE,MAAM,WAAW,GAAI,MAAM,CAAC,IAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAC1F,WAAW,CAAC,IAAI,CAAC;oBACf,MAAM,EAAE,sBAAsB;oBAC9B,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,+BAA+B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,cAAc,qEAAqE;oBAChK,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,IAAI,EAAE,cAAc;oBACpB,OAAO,EAAE,CAAC,QAAQ,CAAC;oBACnB,GAAG,EAAE;wBACH,WAAW,EAAE,2BAA2B,WAAW,GAAG;wBACtD,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;qBAC5C;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 cursorNoDefaultWithoutType: Rule;
@@ -0,0 +1,49 @@
1
+ import { walkSchema } from '../../utils/schema-walker.js';
2
+ function inferType(value) {
3
+ if (value === null)
4
+ return undefined;
5
+ if (typeof value === 'string')
6
+ return 'string';
7
+ if (typeof value === 'number')
8
+ return Number.isInteger(value) ? 'integer' : 'number';
9
+ if (typeof value === 'boolean')
10
+ return 'boolean';
11
+ if (Array.isArray(value))
12
+ return 'array';
13
+ if (typeof value === 'object')
14
+ return 'object';
15
+ return undefined;
16
+ }
17
+ export const cursorNoDefaultWithoutType = {
18
+ id: 'cursor/no-default-without-type',
19
+ severity: 'error',
20
+ description: 'Cursor requires an explicit `type` when a `default` value is present.',
21
+ clients: ['cursor'],
22
+ check(tool, _context) {
23
+ const diagnostics = [];
24
+ walkSchema(tool.inputSchema, (schema, path) => {
25
+ if (schema.default !== undefined && schema.type === undefined) {
26
+ const diagnosticPath = path ? `inputSchema.${path}` : 'inputSchema';
27
+ const inferred = inferType(schema.default);
28
+ diagnostics.push({
29
+ ruleId: 'cursor/no-default-without-type',
30
+ severity: 'error',
31
+ message: `Property at "${diagnosticPath}" has a \`default\` value but no \`type\`. Cursor requires an explicit type when defaults are present.`,
32
+ toolName: tool.name,
33
+ path: diagnosticPath,
34
+ clients: ['cursor'],
35
+ ...(inferred
36
+ ? {
37
+ fix: {
38
+ description: `Add \`"type": "${inferred}"\` inferred from the default value`,
39
+ apply: (s) => ({ ...s, type: inferred }),
40
+ },
41
+ }
42
+ : {}),
43
+ });
44
+ }
45
+ });
46
+ return diagnostics;
47
+ },
48
+ };
49
+ //# sourceMappingURL=cursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../../src/rules/client-specific/cursor.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE1D,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;IACrF,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACjD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IACzC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC/C,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,MAAM,0BAA0B,GAAS;IAC9C,EAAE,EAAE,gCAAgC;IACpC,QAAQ,EAAE,OAAO;IACjB,WAAW,EAAE,uEAAuE;IACpF,OAAO,EAAE,CAAC,QAAQ,CAAC;IAEnB,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,MAAM,CAAC,OAAO,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC9D,MAAM,cAAc,GAAG,IAAI,CAAC,CAAC,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;gBACpE,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBAC3C,WAAW,CAAC,IAAI,CAAC;oBACf,MAAM,EAAE,gCAAgC;oBACxC,QAAQ,EAAE,OAAO;oBACjB,OAAO,EAAE,gBAAgB,cAAc,wGAAwG;oBAC/I,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,IAAI,EAAE,cAAc;oBACpB,OAAO,EAAE,CAAC,QAAQ,CAAC;oBACnB,GAAG,CAAC,QAAQ;wBACV,CAAC,CAAC;4BACE,GAAG,EAAE;gCACH,WAAW,EAAE,kBAAkB,QAAQ,qCAAqC;gCAC5E,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;6BACzC;yBACF;wBACH,CAAC,CAAC,EAAE,CAAC;iBACR,CAAC,CAAC;YACL,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,WAAW,CAAC;IACrB,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Rule } from '../../core/rule.js';
2
+ export declare const geminiNoOptionalWithoutDefault: Rule;
3
+ export declare const geminiNoNestedObjects: Rule;
@@ -0,0 +1,60 @@
1
+ export const geminiNoOptionalWithoutDefault = {
2
+ id: 'gemini/no-optional-without-default',
3
+ severity: 'warning',
4
+ description: 'Gemini handles optional parameters better when they have explicit default values.',
5
+ clients: ['gemini'],
6
+ check(tool, _context) {
7
+ const diagnostics = [];
8
+ const { properties, required } = tool.inputSchema;
9
+ if (!properties)
10
+ return diagnostics;
11
+ const requiredSet = new Set(Array.isArray(required) ? required : []);
12
+ for (const [propName, propSchema] of Object.entries(properties)) {
13
+ if (!requiredSet.has(propName) && propSchema.default === undefined) {
14
+ diagnostics.push({
15
+ ruleId: 'gemini/no-optional-without-default',
16
+ severity: 'warning',
17
+ message: `Optional parameter "${propName}" in tool "${tool.name}" has no \`default\` value. Gemini handles optional params better with explicit defaults.`,
18
+ toolName: tool.name,
19
+ path: `inputSchema.properties.${propName}`,
20
+ clients: ['gemini'],
21
+ });
22
+ }
23
+ }
24
+ return diagnostics;
25
+ },
26
+ };
27
+ function getObjectDepth(schema, depth = 0) {
28
+ if (!schema || typeof schema !== 'object')
29
+ return depth;
30
+ if (!schema.properties)
31
+ return depth;
32
+ let max = depth;
33
+ for (const child of Object.values(schema.properties)) {
34
+ if (child.type === 'object' || child.properties) {
35
+ max = Math.max(max, getObjectDepth(child, depth + 1));
36
+ }
37
+ }
38
+ return max;
39
+ }
40
+ export const geminiNoNestedObjects = {
41
+ id: 'gemini/no-nested-objects',
42
+ severity: 'warning',
43
+ description: 'Gemini has limited support for object properties nested more than 2 levels deep.',
44
+ clients: ['gemini'],
45
+ check(tool, _context) {
46
+ const depth = getObjectDepth(tool.inputSchema);
47
+ if (depth > 2) {
48
+ return [{
49
+ ruleId: 'gemini/no-nested-objects',
50
+ severity: 'warning',
51
+ message: `Tool "${tool.name}" has object properties nested ${depth} level(s) deep. Gemini's function calling has limited support beyond 2 levels.`,
52
+ toolName: tool.name,
53
+ path: 'inputSchema',
54
+ clients: ['gemini'],
55
+ }];
56
+ }
57
+ return [];
58
+ },
59
+ };
60
+ //# sourceMappingURL=gemini.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../../src/rules/client-specific/gemini.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,8BAA8B,GAAS;IAClD,EAAE,EAAE,oCAAoC;IACxC,QAAQ,EAAE,SAAS;IACnB,WAAW,EAAE,mFAAmF;IAChG,OAAO,EAAE,CAAC,QAAQ,CAAC;IAEnB,KAAK,CAAC,IAAa,EAAE,QAAqB;QACxC,MAAM,WAAW,GAAiB,EAAE,CAAC;QACrC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC;QAClD,IAAI,CAAC,UAAU;YAAE,OAAO,WAAW,CAAC;QAEpC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAErE,KAAK,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YAChE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,UAAU,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;gBACnE,WAAW,CAAC,IAAI,CAAC;oBACf,MAAM,EAAE,oCAAoC;oBAC5C,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,uBAAuB,QAAQ,cAAc,IAAI,CAAC,IAAI,2FAA2F;oBAC1J,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,IAAI,EAAE,0BAA0B,QAAQ,EAAE;oBAC1C,OAAO,EAAE,CAAC,QAAQ,CAAC;iBACpB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,WAAW,CAAC;IACrB,CAAC;CACF,CAAC;AAEF,SAAS,cAAc,CAAC,MAAkB,EAAE,KAAK,GAAG,CAAC;IACnD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACxD,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAErC,IAAI,GAAG,GAAG,KAAK,CAAC;IAChB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;QACrD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YAChD,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAS;IACzC,EAAE,EAAE,0BAA0B;IAC9B,QAAQ,EAAE,SAAS;IACnB,WAAW,EAAE,kFAAkF;IAC/F,OAAO,EAAE,CAAC,QAAQ,CAAC;IAEnB,KAAK,CAAC,IAAa,EAAE,QAAqB;QACxC,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC/C,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,OAAO,CAAC;oBACN,MAAM,EAAE,0BAA0B;oBAClC,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,SAAS,IAAI,CAAC,IAAI,kCAAkC,KAAK,gFAAgF;oBAClJ,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,IAAI,EAAE,aAAa;oBACnB,OAAO,EAAE,CAAC,QAAQ,CAAC;iBACpB,CAAC,CAAC;QACL,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;CACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from '../../core/rule.js';
2
+ export declare const vscodeMaxParams: Rule;
@@ -0,0 +1,22 @@
1
+ const MAX_PARAMS = 15;
2
+ export const vscodeMaxParams = {
3
+ id: 'vscode/max-params',
4
+ severity: 'warning',
5
+ description: `VS Code Copilot performance degrades with more than ${MAX_PARAMS} parameters.`,
6
+ clients: ['vscode'],
7
+ check(tool, _context) {
8
+ const count = Object.keys(tool.inputSchema.properties ?? {}).length;
9
+ if (count > MAX_PARAMS) {
10
+ return [{
11
+ ruleId: 'vscode/max-params',
12
+ severity: 'warning',
13
+ message: `Tool "${tool.name}" has ${count} parameters. VS Code Copilot performance degrades with more than ${MAX_PARAMS}. Consider splitting into multiple tools.`,
14
+ toolName: tool.name,
15
+ path: 'inputSchema.properties',
16
+ clients: ['vscode'],
17
+ }];
18
+ }
19
+ return [];
20
+ },
21
+ };
22
+ //# sourceMappingURL=vscode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vscode.js","sourceRoot":"","sources":["../../../src/rules/client-specific/vscode.ts"],"names":[],"mappings":"AAIA,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB,MAAM,CAAC,MAAM,eAAe,GAAS;IACnC,EAAE,EAAE,mBAAmB;IACvB,QAAQ,EAAE,SAAS;IACnB,WAAW,EAAE,uDAAuD,UAAU,cAAc;IAC5F,OAAO,EAAE,CAAC,QAAQ,CAAC;IAEnB,KAAK,CAAC,IAAa,EAAE,QAAqB;QACxC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QACpE,IAAI,KAAK,GAAG,UAAU,EAAE,CAAC;YACvB,OAAO,CAAC;oBACN,MAAM,EAAE,mBAAmB;oBAC3B,QAAQ,EAAE,SAAS;oBACnB,OAAO,EAAE,SAAS,IAAI,CAAC,IAAI,SAAS,KAAK,oEAAoE,UAAU,2CAA2C;oBAClK,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,IAAI,EAAE,wBAAwB;oBAC9B,OAAO,EAAE,CAAC,QAAQ,CAAC;iBACpB,CAAC,CAAC;QACL,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;CACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from '../core/rule.js';
2
+ export declare const descriptionExists: Rule;
@@ -0,0 +1,35 @@
1
+ export const descriptionExists = {
2
+ id: 'description-exists',
3
+ severity: 'warning',
4
+ description: 'Tools and parameters should have descriptions for better LLM tool-use quality.',
5
+ clients: ['claude', 'cursor', 'gemini', 'vscode'],
6
+ check(tool, _context) {
7
+ const diagnostics = [];
8
+ if (!tool.description || tool.description.trim() === '') {
9
+ diagnostics.push({
10
+ ruleId: 'description-exists',
11
+ severity: 'warning',
12
+ message: `Tool "${tool.name}" is missing a description. All clients use descriptions for prompt construction.`,
13
+ toolName: tool.name,
14
+ path: 'description',
15
+ clients: ['claude', 'cursor', 'gemini', 'vscode'],
16
+ });
17
+ }
18
+ if (tool.inputSchema.properties) {
19
+ for (const [propName, propSchema] of Object.entries(tool.inputSchema.properties)) {
20
+ if (!propSchema.description || propSchema.description.trim() === '') {
21
+ diagnostics.push({
22
+ ruleId: 'description-exists',
23
+ severity: 'warning',
24
+ message: `Parameter "${propName}" in tool "${tool.name}" is missing a description.`,
25
+ toolName: tool.name,
26
+ path: `inputSchema.properties.${propName}`,
27
+ clients: ['claude', 'cursor', 'gemini', 'vscode'],
28
+ });
29
+ }
30
+ }
31
+ }
32
+ return diagnostics;
33
+ },
34
+ };
35
+ //# sourceMappingURL=description-exists.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"description-exists.js","sourceRoot":"","sources":["../../src/rules/description-exists.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,iBAAiB,GAAS;IACrC,EAAE,EAAE,oBAAoB;IACxB,QAAQ,EAAE,SAAS;IACnB,WAAW,EAAE,gFAAgF;IAC7F,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;IAEjD,KAAK,CAAC,IAAa,EAAE,QAAqB;QACxC,MAAM,WAAW,GAAiB,EAAE,CAAC;QAErC,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACxD,WAAW,CAAC,IAAI,CAAC;gBACf,MAAM,EAAE,oBAAoB;gBAC5B,QAAQ,EAAE,SAAS;gBACnB,OAAO,EAAE,SAAS,IAAI,CAAC,IAAI,mFAAmF;gBAC9G,QAAQ,EAAE,IAAI,CAAC,IAAI;gBACnB,IAAI,EAAE,aAAa;gBACnB,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;aAClD,CAAC,CAAC;QACL,CAAC;QAED,IAAI,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC;YAChC,KAAK,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;gBACjF,IAAI,CAAC,UAAU,CAAC,WAAW,IAAI,UAAU,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;oBACpE,WAAW,CAAC,IAAI,CAAC;wBACf,MAAM,EAAE,oBAAoB;wBAC5B,QAAQ,EAAE,SAAS;wBACnB,OAAO,EAAE,cAAc,QAAQ,cAAc,IAAI,CAAC,IAAI,6BAA6B;wBACnF,QAAQ,EAAE,IAAI,CAAC,IAAI;wBACnB,IAAI,EAAE,0BAA0B,QAAQ,EAAE;wBAC1C,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;qBAClD,CAAC,CAAC;gBACL,CAAC;YACH,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 allRules: Rule[];