mcp-doctor 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/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/cli/commands/check.d.ts +5 -0
- package/dist/cli/commands/check.js +67 -0
- package/dist/cli/commands/check.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +24 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/locations.d.ts +3 -0
- package/dist/config/locations.js +138 -0
- package/dist/config/locations.js.map +1 -0
- package/dist/config/types.d.ts +37 -0
- package/dist/config/types.js +2 -0
- package/dist/config/types.js.map +1 -0
- package/dist/utils/output.d.ts +6 -0
- package/dist/utils/output.js +102 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/validators/env-vars.d.ts +2 -0
- package/dist/validators/env-vars.js +70 -0
- package/dist/validators/env-vars.js.map +1 -0
- package/dist/validators/index.d.ts +4 -0
- package/dist/validators/index.js +5 -0
- package/dist/validators/index.js.map +1 -0
- package/dist/validators/json-syntax.d.ts +6 -0
- package/dist/validators/json-syntax.js +77 -0
- package/dist/validators/json-syntax.js.map +1 -0
- package/dist/validators/paths.d.ts +2 -0
- package/dist/validators/paths.js +125 -0
- package/dist/validators/paths.js.map +1 -0
- package/dist/validators/server-health.d.ts +2 -0
- package/dist/validators/server-health.js +135 -0
- package/dist/validators/server-health.js.map +1 -0
- package/package.json +45 -0
- package/src/cli/commands/check.ts +103 -0
- package/src/cli/index.ts +29 -0
- package/src/config/locations.ts +141 -0
- package/src/config/types.ts +45 -0
- package/src/utils/output.ts +128 -0
- package/src/validators/env-vars.ts +85 -0
- package/src/validators/index.ts +4 -0
- package/src/validators/json-syntax.ts +83 -0
- package/src/validators/paths.ts +140 -0
- package/src/validators/server-health.ts +165 -0
- package/tsconfig.json +18 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
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,129 @@
|
|
|
1
|
+
# mcp-doctor
|
|
2
|
+
|
|
3
|
+
Diagnose and fix MCP configuration issues across Claude Desktop, Cursor, VS Code, Claude Code, and more.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
MCP configuration is fragile:
|
|
8
|
+
|
|
9
|
+
- **Trailing commas** break JSON parsing silently
|
|
10
|
+
- **Wrong paths** cause "server not found" errors
|
|
11
|
+
- **Missing environment variables** lead to auth failures
|
|
12
|
+
- **Server crashes** happen with unhelpful error messages
|
|
13
|
+
|
|
14
|
+
You end up spending hours debugging issues that should take seconds to find.
|
|
15
|
+
|
|
16
|
+
## The Solution
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g mcp-doctor
|
|
20
|
+
mcp-doctor
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
That's it. It scans your configs and tells you exactly what's wrong:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
𩺠MCP Doctor
|
|
27
|
+
|
|
28
|
+
Found 2 config files:
|
|
29
|
+
|
|
30
|
+
Claude Desktop
|
|
31
|
+
~/Library/Application Support/Claude/claude_desktop_config.json
|
|
32
|
+
|
|
33
|
+
Cursor (project)
|
|
34
|
+
./.cursor/mcp.json
|
|
35
|
+
|
|
36
|
+
ā 1 Error
|
|
37
|
+
|
|
38
|
+
~/.cursor/mcp.json:12
|
|
39
|
+
Trailing comma detected (not allowed in JSON)
|
|
40
|
+
š” Remove the comma before the closing bracket/brace
|
|
41
|
+
|
|
42
|
+
ā ļø 2 Warnings
|
|
43
|
+
|
|
44
|
+
~/.cursor/mcp.json
|
|
45
|
+
Server "github": Environment variable GITHUB_TOKEN is not set
|
|
46
|
+
š” Set it with: export GITHUB_TOKEN="your-value"
|
|
47
|
+
|
|
48
|
+
~/.cursor/mcp.json
|
|
49
|
+
Server "myserver": Using relative path "./server.js"
|
|
50
|
+
š” Consider using absolute path: /Users/you/project/server.js
|
|
51
|
+
|
|
52
|
+
š Server Health: 3 healthy, 1 failed
|
|
53
|
+
|
|
54
|
+
ā filesystem (120ms)
|
|
55
|
+
ā fetch (89ms)
|
|
56
|
+
ā github (340ms)
|
|
57
|
+
ā broken-server
|
|
58
|
+
Timeout after 10000ms
|
|
59
|
+
|
|
60
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
61
|
+
|
|
62
|
+
ā Found 1 error that needs fixing
|
|
63
|
+
ā ļø 3/4 servers responding
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## What It Checks
|
|
67
|
+
|
|
68
|
+
| Check | Description |
|
|
69
|
+
| ------------------------- | ------------------------------------------------------- |
|
|
70
|
+
| **JSON Syntax** | Catches trailing commas, missing brackets, invalid JSON |
|
|
71
|
+
| **Path Validation** | Verifies command paths exist and are executable |
|
|
72
|
+
| **Environment Variables** | Detects missing or empty env vars |
|
|
73
|
+
| **Hardcoded Secrets** | Warns about API keys in config files |
|
|
74
|
+
| **Server Health** | Actually starts each server and tests MCP handshake |
|
|
75
|
+
|
|
76
|
+
## Supported Clients
|
|
77
|
+
|
|
78
|
+
- ā
Claude Desktop (macOS, Windows, Linux)
|
|
79
|
+
- ā
Cursor
|
|
80
|
+
- ā
VS Code (with MCP extension)
|
|
81
|
+
- ā
Claude Code
|
|
82
|
+
- ā
Windsurf
|
|
83
|
+
|
|
84
|
+
## Usage
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Check all configs (default)
|
|
88
|
+
mcp-doctor
|
|
89
|
+
|
|
90
|
+
# Same as above
|
|
91
|
+
mcp-doctor check
|
|
92
|
+
|
|
93
|
+
# Skip server health tests (faster)
|
|
94
|
+
mcp-doctor check --skip-health
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Installation
|
|
98
|
+
|
|
99
|
+
Requires Node.js 18 or later.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# npm
|
|
103
|
+
npm install -g mcp-doctor
|
|
104
|
+
|
|
105
|
+
# Or run directly with npx
|
|
106
|
+
npx mcp-doctor
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## FAQ
|
|
110
|
+
|
|
111
|
+
**Q: It says "No MCP configuration files found"**
|
|
112
|
+
|
|
113
|
+
Make sure you have at least one MCP client configured. The tool looks in standard locations for each client.
|
|
114
|
+
|
|
115
|
+
**Q: Server health check is timing out**
|
|
116
|
+
|
|
117
|
+
Some servers take longer to start. The default timeout is 10 seconds. If your server needs more time, it might indicate a performance issue.
|
|
118
|
+
|
|
119
|
+
**Q: Can it auto-fix issues?**
|
|
120
|
+
|
|
121
|
+
Not yet. For now it tells you what's wrong and how to fix it. Auto-fix for simple issues (like trailing commas) is planned.
|
|
122
|
+
|
|
123
|
+
## Contributing
|
|
124
|
+
|
|
125
|
+
Issues and PRs welcome! https://github.com/Crooj026/mcp-doctor
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
import { getConfigLocations } from '../../config/locations.js';
|
|
3
|
+
import { validateJsonSyntax, validatePaths, validateEnvVars, testServerHealth, } from '../../validators/index.js';
|
|
4
|
+
import { printHeader, printConfigsFound, printValidationResults, printServerResults, printSummary, } from '../../utils/output.js';
|
|
5
|
+
export async function checkCommand(options = {}) {
|
|
6
|
+
printHeader('𩺠MCP Doctor');
|
|
7
|
+
const spinner = ora('Scanning for MCP configurations...').start();
|
|
8
|
+
// Find all config files
|
|
9
|
+
const locations = getConfigLocations();
|
|
10
|
+
const foundConfigs = locations.filter((l) => l.exists);
|
|
11
|
+
spinner.stop();
|
|
12
|
+
// Show what we found
|
|
13
|
+
printConfigsFound(locations);
|
|
14
|
+
if (foundConfigs.length === 0) {
|
|
15
|
+
process.exitCode = 0;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
// Run validators on each config
|
|
19
|
+
const allResults = [];
|
|
20
|
+
const allServerResults = [];
|
|
21
|
+
const configsWithServers = [];
|
|
22
|
+
for (const configLoc of foundConfigs) {
|
|
23
|
+
// JSON syntax check
|
|
24
|
+
const { valid, results: syntaxResults, config } = validateJsonSyntax(configLoc.path);
|
|
25
|
+
allResults.push(...syntaxResults);
|
|
26
|
+
// If JSON is invalid, skip other validators for this file
|
|
27
|
+
if (!valid || !config) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
// Store for server health testing
|
|
31
|
+
configsWithServers.push({ path: configLoc.path, config });
|
|
32
|
+
// Path validation
|
|
33
|
+
allResults.push(...validatePaths(config, configLoc.path));
|
|
34
|
+
// Environment variable validation
|
|
35
|
+
allResults.push(...validateEnvVars(config, configLoc.path));
|
|
36
|
+
}
|
|
37
|
+
// Print validation results
|
|
38
|
+
printValidationResults(allResults);
|
|
39
|
+
// Server health tests (unless skipped)
|
|
40
|
+
if (!options.skipHealth && configsWithServers.length > 0) {
|
|
41
|
+
const healthSpinner = ora('Testing server connectivity...').start();
|
|
42
|
+
for (const { config } of configsWithServers) {
|
|
43
|
+
const results = await testServerHealth(config);
|
|
44
|
+
allServerResults.push(...results);
|
|
45
|
+
}
|
|
46
|
+
healthSpinner.stop();
|
|
47
|
+
// Deduplicate servers by name (same server might be in multiple configs)
|
|
48
|
+
const uniqueServers = new Map();
|
|
49
|
+
for (const result of allServerResults) {
|
|
50
|
+
// Keep the healthy result if we have conflicting results
|
|
51
|
+
if (!uniqueServers.has(result.name) || result.healthy) {
|
|
52
|
+
uniqueServers.set(result.name, result);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
printServerResults(Array.from(uniqueServers.values()));
|
|
56
|
+
}
|
|
57
|
+
// Summary
|
|
58
|
+
const errors = allResults.filter((r) => r.level === 'error').length;
|
|
59
|
+
const warnings = allResults.filter((r) => r.level === 'warning').length;
|
|
60
|
+
const healthyServers = allServerResults.filter((s) => s.healthy).length;
|
|
61
|
+
printSummary(errors, warnings, healthyServers, allServerResults.length);
|
|
62
|
+
// Set exit code
|
|
63
|
+
if (errors > 0) {
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../../src/cli/commands/check.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { checkCommand } from './commands/check.js';
|
|
4
|
+
const program = new Command();
|
|
5
|
+
program
|
|
6
|
+
.name('mcp-doctor')
|
|
7
|
+
.description('Diagnose and fix MCP configuration issues')
|
|
8
|
+
.version('0.1.0');
|
|
9
|
+
program
|
|
10
|
+
.command('check')
|
|
11
|
+
.description('Validate all MCP configuration files')
|
|
12
|
+
.option('--skip-health', 'Skip server health checks')
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
await checkCommand({
|
|
15
|
+
skipHealth: options.skipHealth,
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
// Default command (no subcommand) runs check
|
|
19
|
+
program
|
|
20
|
+
.action(async () => {
|
|
21
|
+
await checkCommand();
|
|
22
|
+
});
|
|
23
|
+
program.parse();
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CAAC,2CAA2C,CAAC;KACxD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,sCAAsC,CAAC;KACnD,MAAM,CAAC,eAAe,EAAE,2BAA2B,CAAC;KACpD,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,YAAY,CAAC;QACjB,UAAU,EAAE,OAAO,CAAC,UAAU;KAC/B,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEL,6CAA6C;AAC7C,OAAO;KACJ,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,YAAY,EAAE,CAAC;AACvB,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { homedir, platform } from 'os';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
export function getConfigLocations(projectDir) {
|
|
5
|
+
const home = homedir();
|
|
6
|
+
const os = platform();
|
|
7
|
+
const cwd = projectDir || process.cwd();
|
|
8
|
+
const locations = [];
|
|
9
|
+
// ============================================
|
|
10
|
+
// Claude Desktop
|
|
11
|
+
// ============================================
|
|
12
|
+
if (os === 'darwin') {
|
|
13
|
+
locations.push({
|
|
14
|
+
client: 'Claude Desktop',
|
|
15
|
+
scope: 'global',
|
|
16
|
+
path: join(home, 'Library/Application Support/Claude/claude_desktop_config.json'),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
else if (os === 'win32') {
|
|
20
|
+
locations.push({
|
|
21
|
+
client: 'Claude Desktop',
|
|
22
|
+
scope: 'global',
|
|
23
|
+
path: join(process.env.APPDATA || join(home, 'AppData/Roaming'), 'Claude/claude_desktop_config.json'),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
// Linux
|
|
28
|
+
locations.push({
|
|
29
|
+
client: 'Claude Desktop',
|
|
30
|
+
scope: 'global',
|
|
31
|
+
path: join(home, '.config/claude/claude_desktop_config.json'),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
// ============================================
|
|
35
|
+
// Claude Code
|
|
36
|
+
// ============================================
|
|
37
|
+
locations.push({
|
|
38
|
+
client: 'Claude Code',
|
|
39
|
+
scope: 'global',
|
|
40
|
+
path: join(home, '.claude.json'),
|
|
41
|
+
});
|
|
42
|
+
locations.push({
|
|
43
|
+
client: 'Claude Code',
|
|
44
|
+
scope: 'project',
|
|
45
|
+
path: join(cwd, '.mcp.json'),
|
|
46
|
+
});
|
|
47
|
+
// ============================================
|
|
48
|
+
// Cursor
|
|
49
|
+
// ============================================
|
|
50
|
+
if (os === 'darwin') {
|
|
51
|
+
locations.push({
|
|
52
|
+
client: 'Cursor',
|
|
53
|
+
scope: 'global',
|
|
54
|
+
path: join(home, 'Library/Application Support/Cursor/User/globalStorage/cursor.mcp/mcp.json'),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else if (os === 'win32') {
|
|
58
|
+
locations.push({
|
|
59
|
+
client: 'Cursor',
|
|
60
|
+
scope: 'global',
|
|
61
|
+
path: join(process.env.APPDATA || join(home, 'AppData/Roaming'), 'Cursor/User/globalStorage/cursor.mcp/mcp.json'),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
locations.push({
|
|
66
|
+
client: 'Cursor',
|
|
67
|
+
scope: 'global',
|
|
68
|
+
path: join(home, '.config/Cursor/User/globalStorage/cursor.mcp/mcp.json'),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// Also check older Cursor location
|
|
72
|
+
locations.push({
|
|
73
|
+
client: 'Cursor',
|
|
74
|
+
scope: 'global',
|
|
75
|
+
path: join(home, '.cursor/mcp.json'),
|
|
76
|
+
});
|
|
77
|
+
locations.push({
|
|
78
|
+
client: 'Cursor',
|
|
79
|
+
scope: 'project',
|
|
80
|
+
path: join(cwd, '.cursor/mcp.json'),
|
|
81
|
+
});
|
|
82
|
+
// ============================================
|
|
83
|
+
// VS Code (MCP extension)
|
|
84
|
+
// ============================================
|
|
85
|
+
if (os === 'darwin') {
|
|
86
|
+
locations.push({
|
|
87
|
+
client: 'VS Code',
|
|
88
|
+
scope: 'global',
|
|
89
|
+
path: join(home, 'Library/Application Support/Code/User/settings.json'),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else if (os === 'win32') {
|
|
93
|
+
locations.push({
|
|
94
|
+
client: 'VS Code',
|
|
95
|
+
scope: 'global',
|
|
96
|
+
path: join(process.env.APPDATA || join(home, 'AppData/Roaming'), 'Code/User/settings.json'),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
locations.push({
|
|
101
|
+
client: 'VS Code',
|
|
102
|
+
scope: 'global',
|
|
103
|
+
path: join(home, '.config/Code/User/settings.json'),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
locations.push({
|
|
107
|
+
client: 'VS Code',
|
|
108
|
+
scope: 'project',
|
|
109
|
+
path: join(cwd, '.vscode/settings.json'),
|
|
110
|
+
});
|
|
111
|
+
// ============================================
|
|
112
|
+
// Windsurf
|
|
113
|
+
// ============================================
|
|
114
|
+
locations.push({
|
|
115
|
+
client: 'Windsurf',
|
|
116
|
+
scope: 'global',
|
|
117
|
+
path: join(home, '.codeium/windsurf/mcp_config.json'),
|
|
118
|
+
});
|
|
119
|
+
// Check existence and return
|
|
120
|
+
return locations.map((loc) => ({
|
|
121
|
+
...loc,
|
|
122
|
+
exists: existsSync(loc.path),
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
export function getServersFromConfig(config) {
|
|
126
|
+
// Try different config formats
|
|
127
|
+
if (config.mcpServers && typeof config.mcpServers === 'object') {
|
|
128
|
+
return config.mcpServers;
|
|
129
|
+
}
|
|
130
|
+
if (config['mcp.servers'] && typeof config['mcp.servers'] === 'object') {
|
|
131
|
+
return config['mcp.servers'];
|
|
132
|
+
}
|
|
133
|
+
if (config.servers && typeof config.servers === 'object') {
|
|
134
|
+
return config.servers;
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=locations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locations.js","sourceRoot":"","sources":["../../src/config/locations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface MCPServerConfig {
|
|
2
|
+
command?: string;
|
|
3
|
+
args?: string[];
|
|
4
|
+
env?: Record<string, string>;
|
|
5
|
+
cwd?: string;
|
|
6
|
+
url?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface MCPConfig {
|
|
9
|
+
mcpServers?: Record<string, MCPServerConfig>;
|
|
10
|
+
'mcp.servers'?: Record<string, MCPServerConfig>;
|
|
11
|
+
servers?: Record<string, MCPServerConfig>;
|
|
12
|
+
}
|
|
13
|
+
export interface ConfigLocation {
|
|
14
|
+
client: string;
|
|
15
|
+
scope: 'global' | 'project';
|
|
16
|
+
path: string;
|
|
17
|
+
exists: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface ValidationResult {
|
|
20
|
+
level: 'error' | 'warning' | 'info';
|
|
21
|
+
code: string;
|
|
22
|
+
message: string;
|
|
23
|
+
file: string;
|
|
24
|
+
line?: number;
|
|
25
|
+
suggestion?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ServerTestResult {
|
|
28
|
+
name: string;
|
|
29
|
+
healthy: boolean;
|
|
30
|
+
responseTime?: number;
|
|
31
|
+
error?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface CheckResults {
|
|
34
|
+
configsFound: ConfigLocation[];
|
|
35
|
+
validationResults: ValidationResult[];
|
|
36
|
+
serverResults: ServerTestResult[];
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/config/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ValidationResult, ServerTestResult, ConfigLocation } from '../config/types.js';
|
|
2
|
+
export declare function printHeader(text: string): void;
|
|
3
|
+
export declare function printConfigsFound(configs: ConfigLocation[]): void;
|
|
4
|
+
export declare function printValidationResults(results: ValidationResult[]): void;
|
|
5
|
+
export declare function printServerResults(results: ServerTestResult[]): void;
|
|
6
|
+
export declare function printSummary(errors: number, warnings: number, healthyServers: number, totalServers: number): void;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export function printHeader(text) {
|
|
3
|
+
console.log(chalk.bold(`\n${text}\n`));
|
|
4
|
+
}
|
|
5
|
+
export function printConfigsFound(configs) {
|
|
6
|
+
const found = configs.filter((c) => c.exists);
|
|
7
|
+
const notFound = configs.filter((c) => !c.exists);
|
|
8
|
+
if (found.length === 0) {
|
|
9
|
+
console.log(chalk.yellow('No MCP configuration files found.\n'));
|
|
10
|
+
console.log(chalk.gray('Looked in:'));
|
|
11
|
+
notFound.slice(0, 5).forEach((c) => {
|
|
12
|
+
console.log(chalk.gray(` ${c.client}: ${c.path}`));
|
|
13
|
+
});
|
|
14
|
+
if (notFound.length > 5) {
|
|
15
|
+
console.log(chalk.gray(` ... and ${notFound.length - 5} more locations`));
|
|
16
|
+
}
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
console.log(chalk.green(`Found ${found.length} config file${found.length > 1 ? 's' : ''}:\n`));
|
|
20
|
+
found.forEach((c) => {
|
|
21
|
+
const scope = c.scope === 'project' ? chalk.gray(' (project)') : '';
|
|
22
|
+
console.log(` ${chalk.cyan(c.client)}${scope}`);
|
|
23
|
+
console.log(chalk.gray(` ${c.path}\n`));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function printValidationResults(results) {
|
|
27
|
+
const errors = results.filter((r) => r.level === 'error');
|
|
28
|
+
const warnings = results.filter((r) => r.level === 'warning');
|
|
29
|
+
if (errors.length > 0) {
|
|
30
|
+
console.log(chalk.red(`ā ${errors.length} Error${errors.length > 1 ? 's' : ''}\n`));
|
|
31
|
+
errors.forEach((e) => {
|
|
32
|
+
const location = e.line ? `:${e.line}` : '';
|
|
33
|
+
console.log(chalk.red(` ${shortPath(e.file)}${location}`));
|
|
34
|
+
console.log(chalk.red(` ${e.message}`));
|
|
35
|
+
if (e.suggestion) {
|
|
36
|
+
console.log(chalk.gray(` š” ${e.suggestion}`));
|
|
37
|
+
}
|
|
38
|
+
console.log('');
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (warnings.length > 0) {
|
|
42
|
+
console.log(chalk.yellow(`ā ļø ${warnings.length} Warning${warnings.length > 1 ? 's' : ''}\n`));
|
|
43
|
+
warnings.forEach((w) => {
|
|
44
|
+
const location = w.line ? `:${w.line}` : '';
|
|
45
|
+
console.log(chalk.yellow(` ${shortPath(w.file)}${location}`));
|
|
46
|
+
console.log(chalk.yellow(` ${w.message}`));
|
|
47
|
+
if (w.suggestion) {
|
|
48
|
+
console.log(chalk.gray(` š” ${w.suggestion}`));
|
|
49
|
+
}
|
|
50
|
+
console.log('');
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function printServerResults(results) {
|
|
55
|
+
if (results.length === 0) {
|
|
56
|
+
console.log(chalk.gray('No servers to test.\n'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const healthy = results.filter((s) => s.healthy);
|
|
60
|
+
const unhealthy = results.filter((s) => !s.healthy);
|
|
61
|
+
console.log(`š Server Health: ${chalk.green(healthy.length)} healthy, ${unhealthy.length > 0 ? chalk.red(unhealthy.length + ' failed') : chalk.gray('0 failed')}\n`);
|
|
62
|
+
healthy.forEach((s) => {
|
|
63
|
+
const time = s.responseTime ? chalk.gray(` (${s.responseTime}ms)`) : '';
|
|
64
|
+
console.log(chalk.green(` ā ${s.name}${time}`));
|
|
65
|
+
});
|
|
66
|
+
unhealthy.forEach((s) => {
|
|
67
|
+
console.log(chalk.red(` ā ${s.name}`));
|
|
68
|
+
if (s.error) {
|
|
69
|
+
console.log(chalk.gray(` ${s.error}`));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
console.log('');
|
|
73
|
+
}
|
|
74
|
+
export function printSummary(errors, warnings, healthyServers, totalServers) {
|
|
75
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
76
|
+
if (errors === 0 && warnings === 0) {
|
|
77
|
+
console.log(chalk.green('\nā All configurations valid'));
|
|
78
|
+
}
|
|
79
|
+
else if (errors === 0) {
|
|
80
|
+
console.log(chalk.yellow(`\nā ļø Configuration valid with ${warnings} warning${warnings > 1 ? 's' : ''}`));
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.log(chalk.red(`\nā Found ${errors} error${errors > 1 ? 's' : ''} that need${errors === 1 ? 's' : ''} fixing`));
|
|
84
|
+
}
|
|
85
|
+
if (totalServers > 0) {
|
|
86
|
+
if (healthyServers === totalServers) {
|
|
87
|
+
console.log(chalk.green(`ā All ${totalServers} server${totalServers > 1 ? 's' : ''} responding`));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.log(chalk.yellow(`ā ļø ${healthyServers}/${totalServers} servers responding`));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
console.log('');
|
|
94
|
+
}
|
|
95
|
+
function shortPath(path) {
|
|
96
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
97
|
+
if (home && path.startsWith(home)) {
|
|
98
|
+
return '~' + path.slice(home.length);
|
|
99
|
+
}
|
|
100
|
+
return path;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/utils/output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAyB;IACzD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAElD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,qCAAqC,CAAC,CAAC,CAAC;QACjE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;QACtC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YACjC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;QACH,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,QAAQ,CAAC,MAAM,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC;QAC7E,CAAC;QACD,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,KAAK,CAAC,MAAM,eAAe,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IAC/F,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;QAClB,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC;QACjD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAA2B;IAChE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;IAE9D,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QACpF,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YACnB,MAAM,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC;YAC5D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YACzC,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;gBACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;YAClD,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,QAAQ,CAAC,MAAM,WAAW,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QAC/F,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YACrB,MAAM,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC;YAC/D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAC5C,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC;gBACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;YAClD,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAA2B;IAC5D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,CAAC;QACjD,OAAO;IACT,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAEpD,OAAO,CAAC,GAAG,CACT,qBAAqB,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,aAC9C,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CACxF,IAAI,CACL,CAAC;IAEF,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;QACpB,MAAM,IAAI,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;QACtB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACxC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,MAAc,EACd,QAAgB,EAChB,cAAsB,EACtB,YAAoB;IAEpB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAExC,IAAI,MAAM,KAAK,CAAC,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAC;IAC3D,CAAC;SAAM,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,kCAAkC,QAAQ,WAAW,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC5G,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,MAAM,SAAS,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,aAAa,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;IACzH,CAAC;IAED,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QACrB,IAAI,cAAc,KAAK,YAAY,EAAE,CAAC;YACpC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,YAAY,UAAU,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC;QACpG,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,MAAM,CAAC,OAAO,cAAc,IAAI,YAAY,qBAAqB,CAAC,CACzE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC;IAC/D,IAAI,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAClC,OAAO,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { getServersFromConfig } from '../config/locations.js';
|
|
2
|
+
export function validateEnvVars(config, configPath) {
|
|
3
|
+
const results = [];
|
|
4
|
+
const servers = getServersFromConfig(config);
|
|
5
|
+
if (!servers) {
|
|
6
|
+
return results;
|
|
7
|
+
}
|
|
8
|
+
for (const [name, serverRaw] of Object.entries(servers)) {
|
|
9
|
+
const server = serverRaw;
|
|
10
|
+
if (!server.env)
|
|
11
|
+
continue;
|
|
12
|
+
for (const [key, value] of Object.entries(server.env)) {
|
|
13
|
+
if (typeof value !== 'string')
|
|
14
|
+
continue;
|
|
15
|
+
// Check for ${VAR} or $VAR references
|
|
16
|
+
const varRefs = value.match(/\$\{([A-Z_][A-Z0-9_]*)\}|\$([A-Z_][A-Z0-9_]*)/g) || [];
|
|
17
|
+
for (const ref of varRefs) {
|
|
18
|
+
const varName = ref.replace(/^\$\{?|\}?$/g, '');
|
|
19
|
+
if (!process.env[varName]) {
|
|
20
|
+
results.push({
|
|
21
|
+
level: 'warning',
|
|
22
|
+
code: 'ENV_VAR_MISSING',
|
|
23
|
+
message: `Server "${name}": Environment variable ${varName} is not set`,
|
|
24
|
+
file: configPath,
|
|
25
|
+
suggestion: `Set it with: export ${varName}="your-value"`,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Check for empty values that look like they should be set
|
|
30
|
+
if (value === '' || value === '""' || value === "''") {
|
|
31
|
+
results.push({
|
|
32
|
+
level: 'warning',
|
|
33
|
+
code: 'ENV_VAR_EMPTY',
|
|
34
|
+
message: `Server "${name}": Environment variable ${key} is empty`,
|
|
35
|
+
file: configPath,
|
|
36
|
+
suggestion: 'This may cause authentication or configuration issues',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// Warn about hardcoded secrets
|
|
40
|
+
const secretKeyPatterns = [
|
|
41
|
+
/^(api[_-]?key|secret|token|password|pwd|auth|credential)/i,
|
|
42
|
+
/_(api[_-]?key|secret|token|password|pwd|auth|credential)$/i,
|
|
43
|
+
];
|
|
44
|
+
const secretValuePatterns = [
|
|
45
|
+
/^sk[-_][a-zA-Z0-9]{20,}$/, // OpenAI-style keys
|
|
46
|
+
/^[a-f0-9]{32,}$/i, // Hex strings (32+ chars)
|
|
47
|
+
/^eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/, // JWT tokens
|
|
48
|
+
/^ghp_[a-zA-Z0-9]{36}$/, // GitHub tokens
|
|
49
|
+
/^github_pat_[a-zA-Z0-9_]{22,}/, // GitHub PAT
|
|
50
|
+
/^xoxb-[0-9]{10,}/, // Slack bot tokens
|
|
51
|
+
/^xoxp-[0-9]{10,}/, // Slack user tokens
|
|
52
|
+
/^AKIA[A-Z0-9]{16}$/, // AWS access keys
|
|
53
|
+
];
|
|
54
|
+
const isSecretKey = secretKeyPatterns.some((p) => p.test(key));
|
|
55
|
+
const looksLikeSecret = secretValuePatterns.some((p) => p.test(value));
|
|
56
|
+
// Only warn if it's not a variable reference
|
|
57
|
+
if ((isSecretKey || looksLikeSecret) && !value.includes('$')) {
|
|
58
|
+
results.push({
|
|
59
|
+
level: 'warning',
|
|
60
|
+
code: 'HARDCODED_SECRET',
|
|
61
|
+
message: `Server "${name}": Possible hardcoded secret in ${key}`,
|
|
62
|
+
file: configPath,
|
|
63
|
+
suggestion: 'Use an environment variable reference like ${' + key.toUpperCase() + '} instead',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return results;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=env-vars.js.map
|