mcp-medic 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/action.yml +57 -0
- package/dist/checks/index.d.ts +6 -0
- package/dist/checks/index.js +17 -0
- package/dist/checks/malformed-schema.d.ts +2 -0
- package/dist/checks/malformed-schema.js +63 -0
- package/dist/checks/missing-description.d.ts +2 -0
- package/dist/checks/missing-description.js +71 -0
- package/dist/checks/missing-required-fields.d.ts +2 -0
- package/dist/checks/missing-required-fields.js +55 -0
- package/dist/checks/sample-call-simulation.d.ts +2 -0
- package/dist/checks/sample-call-simulation.js +239 -0
- package/dist/checks/type-mismatch.d.ts +2 -0
- package/dist/checks/type-mismatch.js +154 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.js +457 -0
- package/dist/config-loader.d.ts +6 -0
- package/dist/config-loader.js +77 -0
- package/dist/conformance.d.ts +10 -0
- package/dist/conformance.js +112 -0
- package/dist/discovery.d.ts +9 -0
- package/dist/discovery.js +76 -0
- package/dist/extension/index.d.ts +79 -0
- package/dist/extension/index.js +125 -0
- package/dist/fleet.d.ts +48 -0
- package/dist/fleet.js +153 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +13 -0
- package/dist/junit.d.ts +10 -0
- package/dist/junit.js +87 -0
- package/dist/orchestrator.d.ts +6 -0
- package/dist/orchestrator.js +60 -0
- package/dist/policy.d.ts +16 -0
- package/dist/policy.js +143 -0
- package/dist/protocol/connect.d.ts +2 -0
- package/dist/protocol/connect.js +417 -0
- package/dist/protocol/index.d.ts +3 -0
- package/dist/protocol/index.js +6 -0
- package/dist/registry.d.ts +16 -0
- package/dist/registry.js +87 -0
- package/dist/report.d.ts +7 -0
- package/dist/report.js +30 -0
- package/dist/types.d.ts +70 -0
- package/dist/types.js +4 -0
- package/dist/watch.d.ts +12 -0
- package/dist/watch.js +85 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 shivam039
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# mcp-doctor
|
|
2
|
+
|
|
3
|
+
Diagnose broken MCP (Model Context Protocol) server configs before they break your agent silently.
|
|
4
|
+
|
|
5
|
+
`mcp-doctor` validates MCP server configurations, executes full protocol initialization handshakes across stdio/SSE/HTTP transports, checks all exposed tool JSON schemas against standard specifications, and simulates sample calls — providing actionable suggestions, in-editor VS Code diagnostics, fleet management, and CI-ready exit codes.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- 🔍 **Auto-Discovery**: Run `mcp-doctor check` with no arguments to auto-discover Claude Desktop, `.mcp.json`, and VS Code/Cursor MCP configuration paths across macOS, Windows, and Linux.
|
|
12
|
+
- 💡 **Auto-Fix Suggestions**: Diagnose issues with clear, actionable fix suggestions using `--show-fixes`.
|
|
13
|
+
- 🌐 **Registry Validation**: Validate published registry entries directly using `mcp-doctor check --registry <server-id>`.
|
|
14
|
+
- 🏢 **Fleet Validation**: Scan and validate monorepos or multi-team configurations with `mcp-doctor check-all "<glob>"`.
|
|
15
|
+
- ⚖️ **Drift Detection**: Catch environment divergence between staging and production configs with `mcp-doctor diff <configA> <configB>`.
|
|
16
|
+
- 📜 **Policy-as-Code**: Enforce organizational constraints (e.g., banned transports, domain allowlists, minimum description lengths) via `.mcp-doctor-policy.json` / `--policy`.
|
|
17
|
+
- 📸 **Snapshot Baseline Mode**: Filter out legacy diagnostics with `--snapshot <baseline.json>` to gate only on newly introduced regressions.
|
|
18
|
+
- 📊 **CI Reporting**: Export standard JUnit XML (`--export-junit <file.xml>`) and JSON (`--export-json <file.json>`) for seamless CI dashboard visualization.
|
|
19
|
+
- 👀 **Watch Mode**: Re-run validation on save using `mcp-doctor watch <path>`.
|
|
20
|
+
- ⚡ **Transport Hardening**: Full handshake validation across stdio, HTTP (with OAuth token refresh), and SSE (with automatic retry resilience).
|
|
21
|
+
- 💻 **VS Code Extension**: In-editor squiggles and hover tooltips showing diagnostics and suggested fixes.
|
|
22
|
+
- 🚦 **CI Usability & Exit Codes**: Strict exit code taxonomy (`0` clean, `1` diagnostic failures, `2` usage/syntax errors) and `--fail-on <error|warning>`.
|
|
23
|
+
- 🤖 **GitHub Action**: Drop-in CI integration via `shivam039/mcp-doctor@main` (or `mcp-doctor-action`).
|
|
24
|
+
- 🧩 **Community Checks**: Conformance test helper (`runCheckConformanceSuite`) to build custom `mcp-doctor-check-*` check plugins.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Run against auto-discovered configs in current project / Claude Desktop
|
|
32
|
+
npx mcp-doctor
|
|
33
|
+
|
|
34
|
+
# Run check on a specific configuration file
|
|
35
|
+
npx mcp-doctor check path/to/config.json
|
|
36
|
+
|
|
37
|
+
# Validate all configs across a monorepo
|
|
38
|
+
npx mcp-doctor check-all "configs/**/*.json"
|
|
39
|
+
|
|
40
|
+
# Compare two configs to detect drift
|
|
41
|
+
npx mcp-doctor diff staging.mcp.json prod.mcp.json
|
|
42
|
+
|
|
43
|
+
# Validate a published registry server directly without a local config
|
|
44
|
+
npx mcp-doctor check --registry @modelcontextprotocol/server-memory
|
|
45
|
+
npx mcp-doctor check --registry smithery:username/my-server
|
|
46
|
+
|
|
47
|
+
# Apply organizational policy rules and export to JUnit XML
|
|
48
|
+
npx mcp-doctor check path/to/config.json --policy .mcp-doctor-policy.json --export-junit results.xml
|
|
49
|
+
|
|
50
|
+
# Display suggested fixes for flagged diagnostics
|
|
51
|
+
npx mcp-doctor check path/to/config.json --show-fixes
|
|
52
|
+
|
|
53
|
+
# Watch mode (re-runs checks on save)
|
|
54
|
+
npx mcp-doctor watch path/to/config.json
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## CLI Options
|
|
60
|
+
|
|
61
|
+
| Command / Flag | Description |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `[check] [path]` | Check target configuration (or auto-discover if path is omitted) |
|
|
64
|
+
| `check-all "<glob>"` | Validate all matching configuration files in fleet |
|
|
65
|
+
| `diff <configA> <configB>` | Detect drift between two configuration files |
|
|
66
|
+
| `check --registry <id>` | Validate a published registry server directly |
|
|
67
|
+
| `watch <path>` | Watch configuration file and re-run checks on file save |
|
|
68
|
+
| `--config <path>` | Explicit configuration path |
|
|
69
|
+
| `--policy <path>` | Apply organizational policy rules (`.mcp-doctor-policy.json`) |
|
|
70
|
+
| `--snapshot <path>` | Compare against baseline snapshot, reporting regressions only |
|
|
71
|
+
| `--update-snapshot <path>` | Save diagnostic report as new baseline snapshot |
|
|
72
|
+
| `--export-junit <file>` | Export report in JUnit XML format |
|
|
73
|
+
| `--export-json <file>` | Export report in JSON format |
|
|
74
|
+
| `--show-fixes` | Show suggested fixes inline under diagnostics |
|
|
75
|
+
| `--fail-on <severity>` | Fail with exit code 1 on `error` (default) or `warning` |
|
|
76
|
+
| `--verbose`, `-v` | Output raw JSON-RPC traffic and debug messages |
|
|
77
|
+
| `--json` | Output full diagnostic report in JSON |
|
|
78
|
+
| `--timeout <ms>` | Per-server handshake timeout in milliseconds (default: `5000`) |
|
|
79
|
+
|
|
80
|
+
### Exit Codes
|
|
81
|
+
|
|
82
|
+
- **`0`**: All checks passed cleanly.
|
|
83
|
+
- **`1`**: Diagnostic failure (one or more errors, or warnings if `--fail-on warning` is set).
|
|
84
|
+
- **`2`**: Configuration or usage error (missing file, JSON parse error, invalid options).
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Policy-as-Code (`.mcp-doctor-policy.json`)
|
|
89
|
+
|
|
90
|
+
Define organization-wide policies that compose with built-in checks:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"bannedTransports": ["stdio"],
|
|
95
|
+
"allowedDomains": ["corp.internal", "mcp.example.com"],
|
|
96
|
+
"minDescriptionLength": 20
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## VS Code Extension
|
|
103
|
+
|
|
104
|
+
The `mcp-doctor` VS Code extension provides:
|
|
105
|
+
- Inline squiggles on `.mcp.json`, `mcp.json`, and `claude_desktop_config.json` files as you edit.
|
|
106
|
+
- Hover tooltips showing the full diagnostic explanation and actionable suggested fix.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## GitHub Action
|
|
111
|
+
|
|
112
|
+
Add MCP config validation to your PR workflow:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
name: Validate MCP Configs
|
|
116
|
+
on: [push, pull_request]
|
|
117
|
+
|
|
118
|
+
jobs:
|
|
119
|
+
validate:
|
|
120
|
+
runs-on: ubuntu-latest
|
|
121
|
+
steps:
|
|
122
|
+
- uses: actions/checkout@v4
|
|
123
|
+
- uses: shivam039/mcp-doctor@main
|
|
124
|
+
with:
|
|
125
|
+
config-path: './.mcp.json'
|
|
126
|
+
fail-on: 'error'
|
|
127
|
+
show-fixes: 'true'
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Check Ecosystem Directory
|
|
133
|
+
|
|
134
|
+
| Check ID | Package | Scope | Description |
|
|
135
|
+
|---|---|---|---|
|
|
136
|
+
| `schema.malformed` | `mcp-doctor` | **Official** | Verifies inputSchema is a valid JSON schema object |
|
|
137
|
+
| `schema.missing-required` | `mcp-doctor` | **Official** | Flags required fields missing from properties |
|
|
138
|
+
| `schema.type-mismatch` | `mcp-doctor` | **Official** | Flags invalid JSON schema types and enum mismatches |
|
|
139
|
+
| `schema.missing-description` | `mcp-doctor` | **Official** | Flags tools and properties missing documentation |
|
|
140
|
+
| `schema.sample-call-simulation` | `mcp-doctor` | **Official** | Simulates and validates synthetic call payloads |
|
|
141
|
+
| `policy.*` | `mcp-doctor` | **Official** | Evaluates policy-as-code rules (transports, domains, length) |
|
|
142
|
+
| `community.strict-typing` | `mcp-doctor-check-strict-typing` | *Community* | Enforces strict property type annotations |
|
|
143
|
+
| `community.no-empty-enums` | `mcp-doctor-check-no-empty-enums` | *Community* | Ensures non-empty enum option lists |
|
|
144
|
+
|
|
145
|
+
To write and publish your own check plugin, see [Authoring Custom Checks](./docs/AUTHORING_CHECKS.md).
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Governance, Stability & Security
|
|
150
|
+
|
|
151
|
+
- 🏛️ [Governance & Project Sustainability](./GOVERNANCE.md)
|
|
152
|
+
- 📜 [1.0 Stability & Deprecation Policy](./docs/STABILITY_POLICY.md)
|
|
153
|
+
- 🔒 [Security Policy & Threat Model](./SECURITY.md)
|
|
154
|
+
- 💡 [RFC Process](./docs/RFC_PROCESS.md)
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Development
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npm install
|
|
162
|
+
npm run build
|
|
163
|
+
npm run typecheck
|
|
164
|
+
npm run test
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT
|
package/action.yml
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: 'mcp-doctor-action'
|
|
2
|
+
description: 'Diagnose broken MCP (Model Context Protocol) server configs in CI before they break your agent silently'
|
|
3
|
+
author: 'mcp-doctor'
|
|
4
|
+
branding:
|
|
5
|
+
icon: 'check-circle'
|
|
6
|
+
color: 'green'
|
|
7
|
+
|
|
8
|
+
inputs:
|
|
9
|
+
config-path:
|
|
10
|
+
description: 'Path to MCP configuration file (defaults to auto-discovery)'
|
|
11
|
+
required: false
|
|
12
|
+
default: ''
|
|
13
|
+
fail-on:
|
|
14
|
+
description: 'Failure threshold: "error" (default) or "warning"'
|
|
15
|
+
required: false
|
|
16
|
+
default: 'error'
|
|
17
|
+
show-fixes:
|
|
18
|
+
description: 'Display actionable fix suggestions under diagnostics'
|
|
19
|
+
required: false
|
|
20
|
+
default: 'true'
|
|
21
|
+
timeout:
|
|
22
|
+
description: 'Handshake timeout per server in milliseconds'
|
|
23
|
+
required: false
|
|
24
|
+
default: '5000'
|
|
25
|
+
verbose:
|
|
26
|
+
description: 'Output raw JSON-RPC traffic for debugging'
|
|
27
|
+
required: false
|
|
28
|
+
default: 'false'
|
|
29
|
+
|
|
30
|
+
runs:
|
|
31
|
+
using: 'composite'
|
|
32
|
+
steps:
|
|
33
|
+
- name: Set up Node.js
|
|
34
|
+
uses: actions/setup-node@v4
|
|
35
|
+
with:
|
|
36
|
+
node-version: '20'
|
|
37
|
+
|
|
38
|
+
- name: Run mcp-doctor
|
|
39
|
+
shell: bash
|
|
40
|
+
run: |
|
|
41
|
+
ARGS="check"
|
|
42
|
+
if [ -n "${{ inputs.config-path }}" ]; then
|
|
43
|
+
ARGS="$ARGS ${{ inputs.config-path }}"
|
|
44
|
+
fi
|
|
45
|
+
if [ "${{ inputs.show-fixes }}" = "true" ]; then
|
|
46
|
+
ARGS="$ARGS --show-fixes"
|
|
47
|
+
fi
|
|
48
|
+
if [ -n "${{ inputs.fail-on }}" ]; then
|
|
49
|
+
ARGS="$ARGS --fail-on ${{ inputs.fail-on }}"
|
|
50
|
+
fi
|
|
51
|
+
if [ -n "${{ inputs.timeout }}" ]; then
|
|
52
|
+
ARGS="$ARGS --timeout ${{ inputs.timeout }}"
|
|
53
|
+
fi
|
|
54
|
+
if [ "${{ inputs.verbose }}" = "true" ]; then
|
|
55
|
+
ARGS="$ARGS --verbose"
|
|
56
|
+
fi
|
|
57
|
+
npx --yes mcp-doctor $ARGS
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { malformedSchemaCheck } from './malformed-schema.js';
|
|
2
|
+
export { missingRequiredFieldsCheck } from './missing-required-fields.js';
|
|
3
|
+
export { typeMismatchCheck } from './type-mismatch.js';
|
|
4
|
+
export { missingDescriptionCheck } from './missing-description.js';
|
|
5
|
+
export { sampleCallSimulationCheck } from './sample-call-simulation.js';
|
|
6
|
+
export declare const allChecks: import("../types.js").Check[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { malformedSchemaCheck } from './malformed-schema.js';
|
|
2
|
+
export { missingRequiredFieldsCheck } from './missing-required-fields.js';
|
|
3
|
+
export { typeMismatchCheck } from './type-mismatch.js';
|
|
4
|
+
export { missingDescriptionCheck } from './missing-description.js';
|
|
5
|
+
export { sampleCallSimulationCheck } from './sample-call-simulation.js';
|
|
6
|
+
import { malformedSchemaCheck } from './malformed-schema.js';
|
|
7
|
+
import { missingRequiredFieldsCheck } from './missing-required-fields.js';
|
|
8
|
+
import { typeMismatchCheck } from './type-mismatch.js';
|
|
9
|
+
import { missingDescriptionCheck } from './missing-description.js';
|
|
10
|
+
import { sampleCallSimulationCheck } from './sample-call-simulation.js';
|
|
11
|
+
export const allChecks = [
|
|
12
|
+
malformedSchemaCheck,
|
|
13
|
+
missingRequiredFieldsCheck,
|
|
14
|
+
typeMismatchCheck,
|
|
15
|
+
missingDescriptionCheck,
|
|
16
|
+
sampleCallSimulationCheck,
|
|
17
|
+
];
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export const malformedSchemaCheck = {
|
|
2
|
+
id: 'schema.malformed',
|
|
3
|
+
description: 'Flags tools whose inputSchema is not a valid JSON Schema object.',
|
|
4
|
+
run(connection) {
|
|
5
|
+
const results = [];
|
|
6
|
+
try {
|
|
7
|
+
if (!connection.tools || !Array.isArray(connection.tools)) {
|
|
8
|
+
return results;
|
|
9
|
+
}
|
|
10
|
+
for (const tool of connection.tools) {
|
|
11
|
+
const schema = tool.inputSchema;
|
|
12
|
+
// Check if inputSchema is an object and not null / array / primitive
|
|
13
|
+
if (schema === null || typeof schema !== 'object' || Array.isArray(schema)) {
|
|
14
|
+
const actualType = schema === null ? 'null' : Array.isArray(schema) ? 'array' : typeof schema;
|
|
15
|
+
results.push({
|
|
16
|
+
checkId: 'schema.malformed',
|
|
17
|
+
severity: 'error',
|
|
18
|
+
message: `Tool "${tool.name}" inputSchema must be a valid JSON Schema object, but got ${actualType}.`,
|
|
19
|
+
serverName: connection.server.name,
|
|
20
|
+
toolName: tool.name,
|
|
21
|
+
details: { actualType, inputSchema: schema },
|
|
22
|
+
suggestedFix: {
|
|
23
|
+
description: 'Replace inputSchema with a valid JSON Schema object, e.g. { "type": "object", "properties": {} }.',
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
// Schema is an object: verify it has 'type' or a combinator / ref
|
|
29
|
+
const schemaObj = schema;
|
|
30
|
+
const hasType = typeof schemaObj.type === 'string' || Array.isArray(schemaObj.type);
|
|
31
|
+
const hasCombinatorOrRef = Boolean(schemaObj.$ref) ||
|
|
32
|
+
Boolean(schemaObj.oneOf) ||
|
|
33
|
+
Boolean(schemaObj.anyOf) ||
|
|
34
|
+
Boolean(schemaObj.allOf);
|
|
35
|
+
if (!hasType && !hasCombinatorOrRef) {
|
|
36
|
+
const hasProperties = Boolean(schemaObj.properties);
|
|
37
|
+
results.push({
|
|
38
|
+
checkId: 'schema.malformed',
|
|
39
|
+
severity: 'warning',
|
|
40
|
+
message: hasProperties
|
|
41
|
+
? `Tool "${tool.name}" inputSchema is missing a "type" or combinator ("$ref", "oneOf", "anyOf", "allOf") field, but defines "properties".`
|
|
42
|
+
: `Tool "${tool.name}" inputSchema is missing a "type" or combinator ("$ref", "oneOf", "anyOf", "allOf") field.`,
|
|
43
|
+
serverName: connection.server.name,
|
|
44
|
+
toolName: tool.name,
|
|
45
|
+
details: { inputSchema: schema },
|
|
46
|
+
suggestedFix: {
|
|
47
|
+
description: 'Add `"type": "object"` to the inputSchema.',
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
results.push({
|
|
55
|
+
checkId: 'schema.malformed',
|
|
56
|
+
severity: 'error',
|
|
57
|
+
message: `check failed internally: ${err instanceof Error ? err.message : String(err)}`,
|
|
58
|
+
serverName: connection.server.name,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return results;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export const missingDescriptionCheck = {
|
|
2
|
+
id: 'schema.missing-description',
|
|
3
|
+
description: 'Flags tools and tool properties that are missing descriptions.',
|
|
4
|
+
run(connection) {
|
|
5
|
+
const results = [];
|
|
6
|
+
try {
|
|
7
|
+
if (!connection.tools || !Array.isArray(connection.tools)) {
|
|
8
|
+
return results;
|
|
9
|
+
}
|
|
10
|
+
for (const tool of connection.tools) {
|
|
11
|
+
// Check top-level tool description
|
|
12
|
+
if (tool.description === undefined ||
|
|
13
|
+
typeof tool.description !== 'string' ||
|
|
14
|
+
tool.description.trim() === '') {
|
|
15
|
+
results.push({
|
|
16
|
+
checkId: 'schema.missing-description',
|
|
17
|
+
severity: 'warning',
|
|
18
|
+
message: `Tool "${tool.name}" is missing a description.`,
|
|
19
|
+
serverName: connection.server.name,
|
|
20
|
+
toolName: tool.name,
|
|
21
|
+
suggestedFix: {
|
|
22
|
+
description: `Add a "description" field to tool "${tool.name}" explaining what the tool does.`,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
// Check property descriptions in inputSchema
|
|
27
|
+
const schema = tool.inputSchema;
|
|
28
|
+
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const schemaObj = schema;
|
|
32
|
+
if (!schemaObj.properties ||
|
|
33
|
+
typeof schemaObj.properties !== 'object' ||
|
|
34
|
+
Array.isArray(schemaObj.properties)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const properties = schemaObj.properties;
|
|
38
|
+
for (const [propName, propDef] of Object.entries(properties)) {
|
|
39
|
+
if (!propDef || typeof propDef !== 'object' || Array.isArray(propDef)) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const propObj = propDef;
|
|
43
|
+
if (propObj.description === undefined ||
|
|
44
|
+
typeof propObj.description !== 'string' ||
|
|
45
|
+
propObj.description.trim() === '') {
|
|
46
|
+
results.push({
|
|
47
|
+
checkId: 'schema.missing-description',
|
|
48
|
+
severity: 'info',
|
|
49
|
+
message: `Property "${propName}" in tool "${tool.name}" is missing a description.`,
|
|
50
|
+
serverName: connection.server.name,
|
|
51
|
+
toolName: tool.name,
|
|
52
|
+
details: { property: propName },
|
|
53
|
+
suggestedFix: {
|
|
54
|
+
description: `Add a "description" field to property "${propName}" in tool "${tool.name}".`,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
results.push({
|
|
63
|
+
checkId: 'schema.missing-description',
|
|
64
|
+
severity: 'error',
|
|
65
|
+
message: `check failed internally: ${err instanceof Error ? err.message : String(err)}`,
|
|
66
|
+
serverName: connection.server.name,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return results;
|
|
70
|
+
},
|
|
71
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const missingRequiredFieldsCheck = {
|
|
2
|
+
id: 'schema.missing-required',
|
|
3
|
+
description: 'Flags required fields listed in inputSchema.required that are not defined in inputSchema.properties.',
|
|
4
|
+
run(connection) {
|
|
5
|
+
const results = [];
|
|
6
|
+
try {
|
|
7
|
+
if (!connection.tools || !Array.isArray(connection.tools)) {
|
|
8
|
+
return results;
|
|
9
|
+
}
|
|
10
|
+
for (const tool of connection.tools) {
|
|
11
|
+
const schema = tool.inputSchema;
|
|
12
|
+
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const schemaObj = schema;
|
|
16
|
+
if (!Array.isArray(schemaObj.required)) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const properties = schemaObj.properties &&
|
|
20
|
+
typeof schemaObj.properties === 'object' &&
|
|
21
|
+
!Array.isArray(schemaObj.properties)
|
|
22
|
+
? schemaObj.properties
|
|
23
|
+
: {};
|
|
24
|
+
for (const reqField of schemaObj.required) {
|
|
25
|
+
if (typeof reqField === 'string' && !(reqField in properties)) {
|
|
26
|
+
results.push({
|
|
27
|
+
checkId: 'schema.missing-required',
|
|
28
|
+
severity: 'error',
|
|
29
|
+
message: `Tool "${tool.name}" lists required field "${reqField}", but it is not defined in "properties".`,
|
|
30
|
+
serverName: connection.server.name,
|
|
31
|
+
toolName: tool.name,
|
|
32
|
+
details: {
|
|
33
|
+
missingField: reqField,
|
|
34
|
+
required: schemaObj.required,
|
|
35
|
+
definedProperties: Object.keys(properties),
|
|
36
|
+
},
|
|
37
|
+
suggestedFix: {
|
|
38
|
+
description: `Define property "${reqField}" under inputSchema.properties, or remove "${reqField}" from inputSchema.required.`,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
results.push({
|
|
47
|
+
checkId: 'schema.missing-required',
|
|
48
|
+
severity: 'error',
|
|
49
|
+
message: `check failed internally: ${err instanceof Error ? err.message : String(err)}`,
|
|
50
|
+
serverName: connection.server.name,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return results;
|
|
54
|
+
},
|
|
55
|
+
};
|