mcp-tool-lint 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +269 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +70 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/linter.d.ts +11 -0
- package/dist/linter.d.ts.map +1 -0
- package/dist/linter.js +28 -0
- package/dist/linter.js.map +1 -0
- package/dist/rules/description-has-verb.d.ts +3 -0
- package/dist/rules/description-has-verb.d.ts.map +1 -0
- package/dist/rules/description-has-verb.js +59 -0
- package/dist/rules/description-has-verb.js.map +1 -0
- package/dist/rules/description-length.d.ts +3 -0
- package/dist/rules/description-length.d.ts.map +1 -0
- package/dist/rules/description-length.js +29 -0
- package/dist/rules/description-length.js.map +1 -0
- package/dist/rules/index.d.ts +10 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +16 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/no-duplicate-names.d.ts +3 -0
- package/dist/rules/no-duplicate-names.d.ts.map +1 -0
- package/dist/rules/no-duplicate-names.js +21 -0
- package/dist/rules/no-duplicate-names.js.map +1 -0
- package/dist/rules/no-vague-verbs.d.ts +3 -0
- package/dist/rules/no-vague-verbs.d.ts.map +1 -0
- package/dist/rules/no-vague-verbs.js +38 -0
- package/dist/rules/no-vague-verbs.js.map +1 -0
- package/dist/rules/require-param-descriptions.d.ts +3 -0
- package/dist/rules/require-param-descriptions.d.ts.map +1 -0
- package/dist/rules/require-param-descriptions.js +23 -0
- package/dist/rules/require-param-descriptions.js.map +1 -0
- package/dist/rules/require-required-array.d.ts +3 -0
- package/dist/rules/require-required-array.d.ts.map +1 -0
- package/dist/rules/require-required-array.js +21 -0
- package/dist/rules/require-required-array.js.map +1 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# mcp-tool-lint
|
|
2
|
+
|
|
3
|
+
Static linter for [MCP (Model Context Protocol)](https://modelcontextprotocol.io) tool definitions. Catches quality defects before deployment so AI agents use your tools correctly.
|
|
4
|
+
|
|
5
|
+
A February 2026 research paper analyzing 1,899 MCP tools from 200 servers found that **97.1% have at least one quality defect** -- unclear descriptions, missing parameter documentation, vague naming, and more. These defects cause AI agents to misuse tools, leading to failed actions and poor user experiences.
|
|
6
|
+
|
|
7
|
+
`mcp-tool-lint` catches these issues statically, before your tools reach production.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install mcp-tool-lint
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### CLI
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Lint a JSON file of tool definitions
|
|
21
|
+
npx mcp-tool-lint tools.json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The JSON file should contain a single tool object or an array of tool objects:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
[
|
|
28
|
+
{
|
|
29
|
+
"name": "get_user",
|
|
30
|
+
"description": "Retrieves a user by their unique identifier from the database",
|
|
31
|
+
"inputSchema": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"properties": {
|
|
34
|
+
"userId": { "type": "string", "description": "The unique user ID" }
|
|
35
|
+
},
|
|
36
|
+
"required": ["userId"]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Output:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
✓ get_user (0 issues)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Programmatic API
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { lintTools, validateTools } from 'mcp-tool-lint';
|
|
52
|
+
|
|
53
|
+
const tools = [
|
|
54
|
+
{
|
|
55
|
+
name: 'get_data',
|
|
56
|
+
description: 'gets data',
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
id: { type: 'string' },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
// Get detailed results
|
|
67
|
+
const results = lintTools(tools);
|
|
68
|
+
for (const result of results) {
|
|
69
|
+
console.log(`${result.tool}: ${result.passed ? 'PASSED' : 'FAILED'}`);
|
|
70
|
+
for (const issue of result.issues) {
|
|
71
|
+
console.log(` [${issue.severity}] ${issue.rule}: ${issue.message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Quick pass/fail check (true if no error-severity issues)
|
|
76
|
+
const ok = validateTools(tools);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Rules
|
|
80
|
+
|
|
81
|
+
### `description-length`
|
|
82
|
+
|
|
83
|
+
**Severity:** warn (error if under 10 characters)
|
|
84
|
+
|
|
85
|
+
Tool descriptions must be at least 20 characters. Descriptions under 10 characters are flagged as errors.
|
|
86
|
+
|
|
87
|
+
| Status | Example |
|
|
88
|
+
|--------|---------|
|
|
89
|
+
| Pass | `"Searches the product catalog by keyword and returns matching items"` |
|
|
90
|
+
| Warn | `"Gets user data"` (15 chars) |
|
|
91
|
+
| Error | `"Get data"` (8 chars) |
|
|
92
|
+
|
|
93
|
+
### `require-param-descriptions`
|
|
94
|
+
|
|
95
|
+
**Severity:** warn
|
|
96
|
+
|
|
97
|
+
Every property in `inputSchema.properties` should have a `description` field. Without descriptions, AI agents must guess what values to pass.
|
|
98
|
+
|
|
99
|
+
| Status | Example |
|
|
100
|
+
|--------|---------|
|
|
101
|
+
| Pass | `{ "userId": { "type": "string", "description": "The unique user ID" } }` |
|
|
102
|
+
| Fail | `{ "userId": { "type": "string" } }` |
|
|
103
|
+
|
|
104
|
+
### `no-vague-verbs`
|
|
105
|
+
|
|
106
|
+
**Severity:** warn
|
|
107
|
+
|
|
108
|
+
Descriptions should not start with or prominently use vague verbs. The following are flagged:
|
|
109
|
+
|
|
110
|
+
- "does", "handles", "manages", "works with", "interacts with", "deals with", "takes care of", "is responsible for", "processes"
|
|
111
|
+
|
|
112
|
+
Use specific verbs like: creates, returns, searches, deletes, updates, fetches, sends, validates, transforms, filters.
|
|
113
|
+
|
|
114
|
+
| Status | Example |
|
|
115
|
+
|--------|---------|
|
|
116
|
+
| Pass | `"Creates a payment record in the billing system"` |
|
|
117
|
+
| Fail | `"Handles payments for the checkout flow"` |
|
|
118
|
+
|
|
119
|
+
### `require-required-array`
|
|
120
|
+
|
|
121
|
+
**Severity:** warn
|
|
122
|
+
|
|
123
|
+
If `inputSchema.properties` has entries, `inputSchema.required` should be present (even if empty `[]`) to make intent explicit. Without it, consumers cannot distinguish "all optional" from "the author forgot."
|
|
124
|
+
|
|
125
|
+
| Status | Example |
|
|
126
|
+
|--------|---------|
|
|
127
|
+
| Pass | `{ "properties": { "id": ... }, "required": ["id"] }` |
|
|
128
|
+
| Pass | `{ "properties": { "id": ... }, "required": [] }` |
|
|
129
|
+
| Fail | `{ "properties": { "id": ... } }` (no required field) |
|
|
130
|
+
|
|
131
|
+
### `description-has-verb`
|
|
132
|
+
|
|
133
|
+
**Severity:** warn
|
|
134
|
+
|
|
135
|
+
Descriptions should contain at least one action verb to convey what the tool does. Checked against a list of 200+ common action verbs.
|
|
136
|
+
|
|
137
|
+
| Status | Example |
|
|
138
|
+
|--------|---------|
|
|
139
|
+
| Pass | `"Creates a new user account"` |
|
|
140
|
+
| Fail | `"A utility for user data in the system"` |
|
|
141
|
+
|
|
142
|
+
### `no-duplicate-names`
|
|
143
|
+
|
|
144
|
+
**Severity:** error
|
|
145
|
+
|
|
146
|
+
When linting multiple tools, no two tools should have the same `name`. Duplicate names cause ambiguity for AI agents selecting tools.
|
|
147
|
+
|
|
148
|
+
| Status | Example |
|
|
149
|
+
|--------|---------|
|
|
150
|
+
| Pass | Tools named `get_user`, `create_user`, `delete_user` |
|
|
151
|
+
| Fail | Two tools both named `get_user` |
|
|
152
|
+
|
|
153
|
+
## CLI Usage
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Basic usage
|
|
157
|
+
npx mcp-tool-lint tools.json
|
|
158
|
+
|
|
159
|
+
# Show help
|
|
160
|
+
npx mcp-tool-lint --help
|
|
161
|
+
|
|
162
|
+
# Show version
|
|
163
|
+
npx mcp-tool-lint --version
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Exit codes:
|
|
167
|
+
- `0` -- all tools pass (warnings are OK)
|
|
168
|
+
- `1` -- at least one tool has an error-severity issue
|
|
169
|
+
|
|
170
|
+
Output format:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
✗ get_data (2 issues)
|
|
174
|
+
warn [description-length] Description is too short (8 chars, min 20)
|
|
175
|
+
warn [require-param-descriptions] Parameter 'id' has no description
|
|
176
|
+
✓ create_user (0 issues)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Custom Rules
|
|
180
|
+
|
|
181
|
+
You can pass custom rules to `lintTools`:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { lintTools, Rule, McpToolDefinition, LintIssue } from 'mcp-tool-lint';
|
|
185
|
+
|
|
186
|
+
const noUnderscores: Rule = {
|
|
187
|
+
name: 'no-underscores-in-name',
|
|
188
|
+
description: 'Tool names should use camelCase, not snake_case',
|
|
189
|
+
check(tool: McpToolDefinition): LintIssue[] {
|
|
190
|
+
if (tool.name.includes('_')) {
|
|
191
|
+
return [{
|
|
192
|
+
rule: 'no-underscores-in-name',
|
|
193
|
+
severity: 'warn',
|
|
194
|
+
message: `Tool name "${tool.name}" uses underscores; consider camelCase`,
|
|
195
|
+
tool: tool.name,
|
|
196
|
+
field: 'name',
|
|
197
|
+
}];
|
|
198
|
+
}
|
|
199
|
+
return [];
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const results = lintTools(tools, { rules: [noUnderscores] });
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
You can also override severity for built-in rules:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
const results = lintTools(tools, {
|
|
210
|
+
severity: {
|
|
211
|
+
'description-length': 'error', // Upgrade to error
|
|
212
|
+
'require-param-descriptions': 'info', // Downgrade to info
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## API Reference
|
|
218
|
+
|
|
219
|
+
### `lintTool(tool, opts?)`
|
|
220
|
+
|
|
221
|
+
Lint a single tool definition. Returns a `LintResult`.
|
|
222
|
+
|
|
223
|
+
### `lintTools(tools, opts?)`
|
|
224
|
+
|
|
225
|
+
Lint an array of tool definitions. Returns `LintResult[]`. Cross-tool rules (like `no-duplicate-names`) operate across the full set.
|
|
226
|
+
|
|
227
|
+
### `validateTools(tools, opts?)`
|
|
228
|
+
|
|
229
|
+
Convenience function. Returns `true` if all tools pass (no error-severity issues).
|
|
230
|
+
|
|
231
|
+
### Types
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
interface McpToolDefinition {
|
|
235
|
+
name: string;
|
|
236
|
+
description: string;
|
|
237
|
+
inputSchema: {
|
|
238
|
+
type: 'object';
|
|
239
|
+
properties?: Record<string, { type?: string; description?: string; [key: string]: unknown }>;
|
|
240
|
+
required?: string[];
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
type Severity = 'error' | 'warn' | 'info';
|
|
245
|
+
|
|
246
|
+
interface LintIssue {
|
|
247
|
+
rule: string;
|
|
248
|
+
severity: Severity;
|
|
249
|
+
message: string;
|
|
250
|
+
tool: string;
|
|
251
|
+
field?: string;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
interface LintResult {
|
|
255
|
+
tool: string;
|
|
256
|
+
issues: LintIssue[];
|
|
257
|
+
passed: boolean; // true if no 'error' severity issues
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
interface Rule {
|
|
261
|
+
name: string;
|
|
262
|
+
description: string;
|
|
263
|
+
check(tool: McpToolDefinition, allTools?: McpToolDefinition[]): LintIssue[];
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## License
|
|
268
|
+
|
|
269
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { lintTools } from './linter.js';
|
|
5
|
+
const RESET = '\x1b[0m';
|
|
6
|
+
const RED = '\x1b[31m';
|
|
7
|
+
const YELLOW = '\x1b[33m';
|
|
8
|
+
const GREEN = '\x1b[32m';
|
|
9
|
+
const CYAN = '\x1b[36m';
|
|
10
|
+
const BOLD = '\x1b[1m';
|
|
11
|
+
function colorize(severity) {
|
|
12
|
+
switch (severity) {
|
|
13
|
+
case 'error': return RED;
|
|
14
|
+
case 'warn': return YELLOW;
|
|
15
|
+
case 'info': return CYAN;
|
|
16
|
+
default: return RESET;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function main() {
|
|
20
|
+
const args = process.argv.slice(2);
|
|
21
|
+
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
22
|
+
console.log(`${BOLD}mcp-tool-lint${RESET} - Static linter for MCP tool definitions\n`);
|
|
23
|
+
console.log('Usage: mcp-tool-lint <tools.json>\n');
|
|
24
|
+
console.log('Options:');
|
|
25
|
+
console.log(' -h, --help Show this help message');
|
|
26
|
+
console.log(' -v, --version Show version\n');
|
|
27
|
+
console.log('The JSON file should contain a single tool object or an array of tool objects.');
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
31
|
+
console.log('0.1.0');
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
const filePath = resolve(args[0]);
|
|
35
|
+
let fileContent;
|
|
36
|
+
try {
|
|
37
|
+
fileContent = readFileSync(filePath, 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
console.error(`${RED}Error: Could not read file "${filePath}"${RESET}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(fileContent);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
console.error(`${RED}Error: Invalid JSON in "${filePath}"${RESET}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
const tools = Array.isArray(parsed) ? parsed : [parsed];
|
|
52
|
+
const results = lintTools(tools);
|
|
53
|
+
let hasErrors = false;
|
|
54
|
+
for (const result of results) {
|
|
55
|
+
const issueCount = result.issues.length;
|
|
56
|
+
const icon = result.passed ? `${GREEN}\u2713${RESET}` : `${RED}\u2717${RESET}`;
|
|
57
|
+
const countLabel = issueCount === 1 ? '1 issue' : `${issueCount} issues`;
|
|
58
|
+
console.log(`${icon} ${BOLD}${result.tool}${RESET} (${countLabel})`);
|
|
59
|
+
for (const issue of result.issues) {
|
|
60
|
+
const color = colorize(issue.severity);
|
|
61
|
+
const severity = issue.severity.padEnd(5);
|
|
62
|
+
console.log(` ${color}${severity}${RESET} [${issue.rule}] ${issue.message}`);
|
|
63
|
+
}
|
|
64
|
+
if (!result.passed)
|
|
65
|
+
hasErrors = true;
|
|
66
|
+
}
|
|
67
|
+
process.exit(hasErrors ? 1 : 0);
|
|
68
|
+
}
|
|
69
|
+
main();
|
|
70
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,KAAK,GAAG,SAAS,CAAC;AACxB,MAAM,GAAG,GAAG,UAAU,CAAC;AACvB,MAAM,MAAM,GAAG,UAAU,CAAC;AAC1B,MAAM,KAAK,GAAG,UAAU,CAAC;AACzB,MAAM,IAAI,GAAG,UAAU,CAAC;AACxB,MAAM,IAAI,GAAG,SAAS,CAAC;AAEvB,SAAS,QAAQ,CAAC,QAAgB;IAChC,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,OAAO,CAAC,CAAC,OAAO,GAAG,CAAC;QACzB,KAAK,MAAM,CAAC,CAAC,OAAO,MAAM,CAAC;QAC3B,KAAK,MAAM,CAAC,CAAC,OAAO,IAAI,CAAC;QACzB,OAAO,CAAC,CAAC,OAAO,KAAK,CAAC;IACxB,CAAC;AACH,CAAC;AAED,SAAS,IAAI;IACX,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEnC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACxE,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,gBAAgB,KAAK,6CAA6C,CAAC,CAAC;QACvF,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;QACnD,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;QAC/C,OAAO,CAAC,GAAG,CAAC,gFAAgF,CAAC,CAAC;QAC9F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAClC,IAAI,WAAmB,CAAC;IAExB,IAAI,CAAC;QACH,WAAW,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,+BAA+B,QAAQ,IAAI,KAAK,EAAE,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,2BAA2B,QAAQ,IAAI,KAAK,EAAE,CAAC,CAAC;QACpE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,KAAK,GAAwB,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC7E,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAEjC,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;QACxC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,SAAS,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,SAAS,KAAK,EAAE,CAAC;QAC/E,MAAM,UAAU,GAAG,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,UAAU,SAAS,CAAC;QAEzE,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,KAAK,KAAK,UAAU,GAAG,CAAC,CAAC;QAErE,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YACvC,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAC1C,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,GAAG,QAAQ,GAAG,KAAK,KAAK,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAChF,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,SAAS,GAAG,IAAI,CAAC;IACvC,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { lintTool, lintTools, validateTools } from './linter.js';
|
|
2
|
+
export type { LintOptions } from './linter.js';
|
|
3
|
+
export { allRules, descriptionLength, requireParamDescriptions, noVagueVerbs, requireRequiredArray, descriptionHasVerb, noDuplicateNames } from './rules/index.js';
|
|
4
|
+
export type { McpToolDefinition, Severity, LintIssue, LintResult, Rule } from './types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACnK,YAAY,EAAE,iBAAiB,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/linter.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { McpToolDefinition, LintResult, Rule, Severity } from './types.js';
|
|
2
|
+
export interface LintOptions {
|
|
3
|
+
rules?: Rule[];
|
|
4
|
+
severity?: {
|
|
5
|
+
[ruleName: string]: Severity;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export declare function lintTool(tool: McpToolDefinition, opts?: LintOptions, allTools?: McpToolDefinition[]): LintResult;
|
|
9
|
+
export declare function lintTools(tools: McpToolDefinition[], opts?: LintOptions): LintResult[];
|
|
10
|
+
export declare function validateTools(tools: McpToolDefinition[], opts?: LintOptions): boolean;
|
|
11
|
+
//# sourceMappingURL=linter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linter.d.ts","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAa,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtF,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IACf,QAAQ,CAAC,EAAE;QACT,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC;KAC9B,CAAC;CACH;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,iBAAiB,EAAE,IAAI,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,iBAAiB,EAAE,GAAG,UAAU,CAqBhH;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,iBAAiB,EAAE,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,UAAU,EAAE,CAEtF;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,EAAE,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAGrF"}
|
package/dist/linter.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { allRules } from './rules/index.js';
|
|
2
|
+
export function lintTool(tool, opts, allTools) {
|
|
3
|
+
const rules = opts?.rules ?? allRules;
|
|
4
|
+
const severityOverrides = opts?.severity ?? {};
|
|
5
|
+
const issues = [];
|
|
6
|
+
for (const rule of rules) {
|
|
7
|
+
const ruleIssues = rule.check(tool, allTools);
|
|
8
|
+
for (const issue of ruleIssues) {
|
|
9
|
+
if (severityOverrides[issue.rule]) {
|
|
10
|
+
issue.severity = severityOverrides[issue.rule];
|
|
11
|
+
}
|
|
12
|
+
issues.push(issue);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
tool: tool.name,
|
|
17
|
+
issues,
|
|
18
|
+
passed: !issues.some(i => i.severity === 'error'),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function lintTools(tools, opts) {
|
|
22
|
+
return tools.map(tool => lintTool(tool, opts, tools));
|
|
23
|
+
}
|
|
24
|
+
export function validateTools(tools, opts) {
|
|
25
|
+
const results = lintTools(tools, opts);
|
|
26
|
+
return results.every(r => r.passed);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=linter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linter.js","sourceRoot":"","sources":["../src/linter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAS5C,MAAM,UAAU,QAAQ,CAAC,IAAuB,EAAE,IAAkB,EAAE,QAA8B;IAClG,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,QAAQ,CAAC;IACtC,MAAM,iBAAiB,GAAG,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC;IAE/C,MAAM,MAAM,GAAgB,EAAE,CAAC;IAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC9C,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;YAC/B,IAAI,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClC,KAAK,CAAC,QAAQ,GAAG,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACjD,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM;QACN,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;KAClD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAA0B,EAAE,IAAkB;IACtE,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAA0B,EAAE,IAAkB;IAC1E,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACvC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"description-has-verb.d.ts","sourceRoot":"","sources":["../../src/rules/description-has-verb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAgC,MAAM,aAAa,CAAC;AAuCjE,eAAO,MAAM,kBAAkB,EAAE,IAyBhC,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const ACTION_VERBS = [
|
|
2
|
+
'accept', 'add', 'aggregate', 'analyze', 'append', 'apply', 'authenticate',
|
|
3
|
+
'build', 'calculate', 'cancel', 'capture', 'check', 'clear', 'clone', 'close',
|
|
4
|
+
'collect', 'combine', 'compare', 'compile', 'compose', 'compute', 'configure',
|
|
5
|
+
'connect', 'convert', 'copy', 'count', 'create', 'decode', 'decrypt', 'delete',
|
|
6
|
+
'deliver', 'deploy', 'detect', 'determine', 'disable', 'disconnect', 'display',
|
|
7
|
+
'download', 'emit', 'enable', 'encode', 'encrypt', 'establish', 'evaluate',
|
|
8
|
+
'execute', 'export', 'extract', 'fetch', 'filter', 'find', 'flush', 'format',
|
|
9
|
+
'forward', 'generate', 'get', 'grant', 'group', 'import', 'index', 'initialize',
|
|
10
|
+
'inject', 'insert', 'inspect', 'install', 'invoke', 'iterate', 'join', 'launch',
|
|
11
|
+
'link', 'list', 'listen', 'load', 'locate', 'log', 'look', 'map', 'match',
|
|
12
|
+
'measure', 'merge', 'migrate', 'modify', 'monitor', 'move', 'normalize',
|
|
13
|
+
'notify', 'open', 'optimize', 'orchestrate', 'output', 'override', 'parse',
|
|
14
|
+
'patch', 'pause', 'perform', 'persist', 'ping', 'poll', 'populate', 'post',
|
|
15
|
+
'print', 'provision', 'publish', 'pull', 'purge', 'push', 'put', 'query',
|
|
16
|
+
'queue', 'read', 'receive', 'record', 'redirect', 'reduce', 'refresh',
|
|
17
|
+
'register', 'reject', 'reload', 'remove', 'rename', 'render', 'replace',
|
|
18
|
+
'replicate', 'report', 'request', 'reset', 'resize', 'resolve', 'restart',
|
|
19
|
+
'restore', 'retrieve', 'return', 'revoke', 'rollback', 'rotate', 'route',
|
|
20
|
+
'run', 'save', 'scan', 'schedule', 'scrape', 'search', 'select', 'send',
|
|
21
|
+
'serialize', 'set', 'shut', 'sign', 'snapshot', 'sort', 'spawn', 'split',
|
|
22
|
+
'start', 'stop', 'store', 'stream', 'submit', 'subscribe', 'summarize',
|
|
23
|
+
'suspend', 'sync', 'terminate', 'test', 'toggle', 'trace', 'track',
|
|
24
|
+
'transfer', 'transform', 'translate', 'trigger', 'truncate', 'uninstall',
|
|
25
|
+
'unlink', 'unlock', 'unsubscribe', 'update', 'upgrade', 'upload', 'upsert',
|
|
26
|
+
'validate', 'verify', 'watch', 'write',
|
|
27
|
+
// Past tense / -s / -ing forms will be matched by stemming below
|
|
28
|
+
'creates', 'returns', 'searches', 'deletes', 'updates', 'fetches', 'sends',
|
|
29
|
+
'validates', 'transforms', 'filters', 'reads', 'writes', 'lists', 'finds',
|
|
30
|
+
'checks', 'runs', 'sets', 'gets', 'adds', 'removes', 'moves', 'copies',
|
|
31
|
+
'loads', 'saves', 'starts', 'stops', 'opens', 'closes', 'connects',
|
|
32
|
+
'disconnects', 'enables', 'disables', 'parses', 'formats', 'converts',
|
|
33
|
+
'computes', 'calculates', 'generates', 'extracts', 'inserts', 'selects',
|
|
34
|
+
];
|
|
35
|
+
const verbSet = new Set(ACTION_VERBS);
|
|
36
|
+
export const descriptionHasVerb = {
|
|
37
|
+
name: 'description-has-verb',
|
|
38
|
+
description: 'Description should contain at least one action verb',
|
|
39
|
+
check(tool) {
|
|
40
|
+
const issues = [];
|
|
41
|
+
const desc = (tool.description || '').toLowerCase();
|
|
42
|
+
const words = desc.split(/\s+/);
|
|
43
|
+
const hasVerb = words.some(word => {
|
|
44
|
+
const cleaned = word.replace(/[^a-z]/g, '');
|
|
45
|
+
return verbSet.has(cleaned);
|
|
46
|
+
});
|
|
47
|
+
if (!hasVerb) {
|
|
48
|
+
issues.push({
|
|
49
|
+
rule: 'description-has-verb',
|
|
50
|
+
severity: 'warn',
|
|
51
|
+
message: 'Description should contain at least one action verb (e.g., "creates", "returns", "searches")',
|
|
52
|
+
tool: tool.name,
|
|
53
|
+
field: 'description',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return issues;
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=description-has-verb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"description-has-verb.js","sourceRoot":"","sources":["../../src/rules/description-has-verb.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"description-length.d.ts","sourceRoot":"","sources":["../../src/rules/description-length.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAgC,MAAM,aAAa,CAAC;AAEjE,eAAO,MAAM,iBAAiB,EAAE,IA4B/B,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export const descriptionLength = {
|
|
2
|
+
name: 'description-length',
|
|
3
|
+
description: 'Tool description must be at least 20 characters (error if < 10)',
|
|
4
|
+
check(tool) {
|
|
5
|
+
const issues = [];
|
|
6
|
+
const desc = (tool.description || '').trim();
|
|
7
|
+
const len = desc.length;
|
|
8
|
+
if (len < 10) {
|
|
9
|
+
issues.push({
|
|
10
|
+
rule: 'description-length',
|
|
11
|
+
severity: 'error',
|
|
12
|
+
message: `Description is too short (${len} chars, min 10)`,
|
|
13
|
+
tool: tool.name,
|
|
14
|
+
field: 'description',
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
else if (len < 20) {
|
|
18
|
+
issues.push({
|
|
19
|
+
rule: 'description-length',
|
|
20
|
+
severity: 'warn',
|
|
21
|
+
message: `Description is too short (${len} chars, min 20)`,
|
|
22
|
+
tool: tool.name,
|
|
23
|
+
field: 'description',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return issues;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=description-length.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"description-length.js","sourceRoot":"","sources":["../../src/rules/description-length.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,iBAAiB,GAAS;IACrC,IAAI,EAAE,oBAAoB;IAC1B,WAAW,EAAE,iEAAiE;IAC9E,KAAK,CAAC,IAAuB;QAC3B,MAAM,MAAM,GAAgB,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC;QAExB,IAAI,GAAG,GAAG,EAAE,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,oBAAoB;gBAC1B,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,6BAA6B,GAAG,iBAAiB;gBAC1D,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,aAAa;aACrB,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,GAAG,GAAG,EAAE,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,oBAAoB;gBAC1B,QAAQ,EAAE,MAAM;gBAChB,OAAO,EAAE,6BAA6B,GAAG,iBAAiB;gBAC1D,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,aAAa;aACrB,CAAC,CAAC;QACL,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Rule } from '../types.js';
|
|
2
|
+
import { descriptionLength } from './description-length.js';
|
|
3
|
+
import { requireParamDescriptions } from './require-param-descriptions.js';
|
|
4
|
+
import { noVagueVerbs } from './no-vague-verbs.js';
|
|
5
|
+
import { requireRequiredArray } from './require-required-array.js';
|
|
6
|
+
import { descriptionHasVerb } from './description-has-verb.js';
|
|
7
|
+
import { noDuplicateNames } from './no-duplicate-names.js';
|
|
8
|
+
export declare const allRules: Rule[];
|
|
9
|
+
export { descriptionLength, requireParamDescriptions, noVagueVerbs, requireRequiredArray, descriptionHasVerb, noDuplicateNames, };
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,eAAO,MAAM,QAAQ,EAAE,IAAI,EAO1B,CAAC;AAEF,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,GACjB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { descriptionLength } from './description-length.js';
|
|
2
|
+
import { requireParamDescriptions } from './require-param-descriptions.js';
|
|
3
|
+
import { noVagueVerbs } from './no-vague-verbs.js';
|
|
4
|
+
import { requireRequiredArray } from './require-required-array.js';
|
|
5
|
+
import { descriptionHasVerb } from './description-has-verb.js';
|
|
6
|
+
import { noDuplicateNames } from './no-duplicate-names.js';
|
|
7
|
+
export const allRules = [
|
|
8
|
+
descriptionLength,
|
|
9
|
+
requireParamDescriptions,
|
|
10
|
+
noVagueVerbs,
|
|
11
|
+
requireRequiredArray,
|
|
12
|
+
descriptionHasVerb,
|
|
13
|
+
noDuplicateNames,
|
|
14
|
+
];
|
|
15
|
+
export { descriptionLength, requireParamDescriptions, noVagueVerbs, requireRequiredArray, descriptionHasVerb, noDuplicateNames, };
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,MAAM,CAAC,MAAM,QAAQ,GAAW;IAC9B,iBAAiB;IACjB,wBAAwB;IACxB,YAAY;IACZ,oBAAoB;IACpB,kBAAkB;IAClB,gBAAgB;CACjB,CAAC;AAEF,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,GACjB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-duplicate-names.d.ts","sourceRoot":"","sources":["../../src/rules/no-duplicate-names.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAgC,MAAM,aAAa,CAAC;AAEjE,eAAO,MAAM,gBAAgB,EAAE,IAsB9B,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const noDuplicateNames = {
|
|
2
|
+
name: 'no-duplicate-names',
|
|
3
|
+
description: 'Tool names must be unique across the set',
|
|
4
|
+
check(tool, allTools) {
|
|
5
|
+
const issues = [];
|
|
6
|
+
if (!allTools || allTools.length <= 1)
|
|
7
|
+
return issues;
|
|
8
|
+
const duplicates = allTools.filter(t => t !== tool && t.name === tool.name);
|
|
9
|
+
if (duplicates.length > 0) {
|
|
10
|
+
issues.push({
|
|
11
|
+
rule: 'no-duplicate-names',
|
|
12
|
+
severity: 'error',
|
|
13
|
+
message: `Duplicate tool name "${tool.name}" found`,
|
|
14
|
+
tool: tool.name,
|
|
15
|
+
field: 'name',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return issues;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=no-duplicate-names.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-duplicate-names.js","sourceRoot":"","sources":["../../src/rules/no-duplicate-names.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,gBAAgB,GAAS;IACpC,IAAI,EAAE,oBAAoB;IAC1B,WAAW,EAAE,0CAA0C;IACvD,KAAK,CAAC,IAAuB,EAAE,QAA8B;QAC3D,MAAM,MAAM,GAAgB,EAAE,CAAC;QAE/B,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,IAAI,CAAC;YAAE,OAAO,MAAM,CAAC;QAErD,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC;QAE5E,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,oBAAoB;gBAC1B,QAAQ,EAAE,OAAO;gBACjB,OAAO,EAAE,wBAAwB,IAAI,CAAC,IAAI,SAAS;gBACnD,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,MAAM;aACd,CAAC,CAAC;QACL,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-vague-verbs.d.ts","sourceRoot":"","sources":["../../src/rules/no-vague-verbs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAgC,MAAM,aAAa,CAAC;AAmBjE,eAAO,MAAM,YAAY,EAAE,IAuB1B,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const VAGUE_PATTERNS = [
|
|
2
|
+
'does',
|
|
3
|
+
'handles',
|
|
4
|
+
'manages',
|
|
5
|
+
'works with',
|
|
6
|
+
'interacts with',
|
|
7
|
+
'deals with',
|
|
8
|
+
'takes care of',
|
|
9
|
+
'is responsible for',
|
|
10
|
+
'processes',
|
|
11
|
+
];
|
|
12
|
+
const SPECIFIC_VERB_SUGGESTIONS = [
|
|
13
|
+
'creates', 'returns', 'searches', 'deletes', 'updates',
|
|
14
|
+
'fetches', 'sends', 'validates', 'transforms', 'filters',
|
|
15
|
+
];
|
|
16
|
+
export const noVagueVerbs = {
|
|
17
|
+
name: 'no-vague-verbs',
|
|
18
|
+
description: 'Description should not use vague verbs like "handles", "manages", "does"',
|
|
19
|
+
check(tool) {
|
|
20
|
+
const issues = [];
|
|
21
|
+
const desc = (tool.description || '').toLowerCase();
|
|
22
|
+
for (const pattern of VAGUE_PATTERNS) {
|
|
23
|
+
const regex = new RegExp(`\\b${pattern}\\b`, 'i');
|
|
24
|
+
if (regex.test(desc)) {
|
|
25
|
+
issues.push({
|
|
26
|
+
rule: 'no-vague-verbs',
|
|
27
|
+
severity: 'warn',
|
|
28
|
+
message: `Description uses vague verb "${pattern}". Use specific verbs like: ${SPECIFIC_VERB_SUGGESTIONS.join(', ')}`,
|
|
29
|
+
tool: tool.name,
|
|
30
|
+
field: 'description',
|
|
31
|
+
});
|
|
32
|
+
break; // One issue per tool is enough
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return issues;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=no-vague-verbs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-vague-verbs.js","sourceRoot":"","sources":["../../src/rules/no-vague-verbs.ts"],"names":[],"mappings":"AAEA,MAAM,cAAc,GAAG;IACrB,MAAM;IACN,SAAS;IACT,SAAS;IACT,YAAY;IACZ,gBAAgB;IAChB,YAAY;IACZ,eAAe;IACf,oBAAoB;IACpB,WAAW;CACZ,CAAC;AAEF,MAAM,yBAAyB,GAAG;IAChC,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS;IACtD,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS;CACzD,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAS;IAChC,IAAI,EAAE,gBAAgB;IACtB,WAAW,EAAE,0EAA0E;IACvF,KAAK,CAAC,IAAuB;QAC3B,MAAM,MAAM,GAAgB,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAEpD,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,MAAM,OAAO,KAAK,EAAE,GAAG,CAAC,CAAC;YAClD,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACrB,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,gBAAgB;oBACtB,QAAQ,EAAE,MAAM;oBAChB,OAAO,EAAE,gCAAgC,OAAO,+BAA+B,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;oBACrH,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,KAAK,EAAE,aAAa;iBACrB,CAAC,CAAC;gBACH,MAAM,CAAC,+BAA+B;YACxC,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-param-descriptions.d.ts","sourceRoot":"","sources":["../../src/rules/require-param-descriptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAgC,MAAM,aAAa,CAAC;AAEjE,eAAO,MAAM,wBAAwB,EAAE,IAuBtC,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const requireParamDescriptions = {
|
|
2
|
+
name: 'require-param-descriptions',
|
|
3
|
+
description: 'Every property in inputSchema.properties should have a description',
|
|
4
|
+
check(tool) {
|
|
5
|
+
const issues = [];
|
|
6
|
+
const properties = tool.inputSchema?.properties;
|
|
7
|
+
if (!properties)
|
|
8
|
+
return issues;
|
|
9
|
+
for (const [paramName, paramDef] of Object.entries(properties)) {
|
|
10
|
+
if (!paramDef.description || paramDef.description.trim().length === 0) {
|
|
11
|
+
issues.push({
|
|
12
|
+
rule: 'require-param-descriptions',
|
|
13
|
+
severity: 'warn',
|
|
14
|
+
message: `Parameter '${paramName}' has no description`,
|
|
15
|
+
tool: tool.name,
|
|
16
|
+
field: `inputSchema.properties.${paramName}.description`,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return issues;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=require-param-descriptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-param-descriptions.js","sourceRoot":"","sources":["../../src/rules/require-param-descriptions.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,wBAAwB,GAAS;IAC5C,IAAI,EAAE,4BAA4B;IAClC,WAAW,EAAE,oEAAoE;IACjF,KAAK,CAAC,IAAuB;QAC3B,MAAM,MAAM,GAAgB,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC;QAEhD,IAAI,CAAC,UAAU;YAAE,OAAO,MAAM,CAAC;QAE/B,KAAK,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/D,IAAI,CAAC,QAAQ,CAAC,WAAW,IAAI,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtE,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,4BAA4B;oBAClC,QAAQ,EAAE,MAAM;oBAChB,OAAO,EAAE,cAAc,SAAS,sBAAsB;oBACtD,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,KAAK,EAAE,0BAA0B,SAAS,cAAc;iBACzD,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-required-array.d.ts","sourceRoot":"","sources":["../../src/rules/require-required-array.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAgC,MAAM,aAAa,CAAC;AAEjE,eAAO,MAAM,oBAAoB,EAAE,IAqBlC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const requireRequiredArray = {
|
|
2
|
+
name: 'require-required-array',
|
|
3
|
+
description: 'inputSchema should declare a required array when properties are defined',
|
|
4
|
+
check(tool) {
|
|
5
|
+
const issues = [];
|
|
6
|
+
const properties = tool.inputSchema?.properties;
|
|
7
|
+
if (properties && Object.keys(properties).length > 0) {
|
|
8
|
+
if (!Array.isArray(tool.inputSchema.required)) {
|
|
9
|
+
issues.push({
|
|
10
|
+
rule: 'require-required-array',
|
|
11
|
+
severity: 'warn',
|
|
12
|
+
message: 'inputSchema has properties but no "required" array — declare required fields explicitly (even if empty)',
|
|
13
|
+
tool: tool.name,
|
|
14
|
+
field: 'inputSchema.required',
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return issues;
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=require-required-array.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-required-array.js","sourceRoot":"","sources":["../../src/rules/require-required-array.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,oBAAoB,GAAS;IACxC,IAAI,EAAE,wBAAwB;IAC9B,WAAW,EAAE,yEAAyE;IACtF,KAAK,CAAC,IAAuB;QAC3B,MAAM,MAAM,GAAgB,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC;QAEhD,IAAI,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC9C,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,wBAAwB;oBAC9B,QAAQ,EAAE,MAAM;oBAChB,OAAO,EAAE,yGAAyG;oBAClH,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,KAAK,EAAE,sBAAsB;iBAC9B,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface McpToolDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: 'object';
|
|
6
|
+
properties?: Record<string, {
|
|
7
|
+
type?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
enum?: string[];
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}>;
|
|
12
|
+
required?: string[];
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export type Severity = 'error' | 'warn' | 'info';
|
|
16
|
+
export interface LintIssue {
|
|
17
|
+
rule: string;
|
|
18
|
+
severity: Severity;
|
|
19
|
+
message: string;
|
|
20
|
+
tool: string;
|
|
21
|
+
field?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface LintResult {
|
|
24
|
+
tool: string;
|
|
25
|
+
issues: LintIssue[];
|
|
26
|
+
passed: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface Rule {
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
check(tool: McpToolDefinition, allTools?: McpToolDefinition[]): LintIssue[];
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;YAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;YACd,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;YAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;SACxB,CAAC,CAAC;QACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AAED,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAEjD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,IAAI,EAAE,iBAAiB,EAAE,QAAQ,CAAC,EAAE,iBAAiB,EAAE,GAAG,SAAS,EAAE,CAAC;CAC7E"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-tool-lint",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Static linter for MCP tool definitions — catch quality defects before deployment",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"mcp-tool-lint": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"test": "tsc && node --experimental-strip-types --no-warnings --test tests/*.test.ts"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"linting",
|
|
19
|
+
"tools",
|
|
20
|
+
"ai",
|
|
21
|
+
"claude"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"typescript": "^5.9.3"
|
|
32
|
+
}
|
|
33
|
+
}
|