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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +129 -0
  3. package/dist/cli/commands/check.d.ts +5 -0
  4. package/dist/cli/commands/check.js +67 -0
  5. package/dist/cli/commands/check.js.map +1 -0
  6. package/dist/cli/index.d.ts +2 -0
  7. package/dist/cli/index.js +24 -0
  8. package/dist/cli/index.js.map +1 -0
  9. package/dist/config/locations.d.ts +3 -0
  10. package/dist/config/locations.js +138 -0
  11. package/dist/config/locations.js.map +1 -0
  12. package/dist/config/types.d.ts +37 -0
  13. package/dist/config/types.js +2 -0
  14. package/dist/config/types.js.map +1 -0
  15. package/dist/utils/output.d.ts +6 -0
  16. package/dist/utils/output.js +102 -0
  17. package/dist/utils/output.js.map +1 -0
  18. package/dist/validators/env-vars.d.ts +2 -0
  19. package/dist/validators/env-vars.js +70 -0
  20. package/dist/validators/env-vars.js.map +1 -0
  21. package/dist/validators/index.d.ts +4 -0
  22. package/dist/validators/index.js +5 -0
  23. package/dist/validators/index.js.map +1 -0
  24. package/dist/validators/json-syntax.d.ts +6 -0
  25. package/dist/validators/json-syntax.js +77 -0
  26. package/dist/validators/json-syntax.js.map +1 -0
  27. package/dist/validators/paths.d.ts +2 -0
  28. package/dist/validators/paths.js +125 -0
  29. package/dist/validators/paths.js.map +1 -0
  30. package/dist/validators/server-health.d.ts +2 -0
  31. package/dist/validators/server-health.js +135 -0
  32. package/dist/validators/server-health.js.map +1 -0
  33. package/package.json +45 -0
  34. package/src/cli/commands/check.ts +103 -0
  35. package/src/cli/index.ts +29 -0
  36. package/src/config/locations.ts +141 -0
  37. package/src/config/types.ts +45 -0
  38. package/src/utils/output.ts +128 -0
  39. package/src/validators/env-vars.ts +85 -0
  40. package/src/validators/index.ts +4 -0
  41. package/src/validators/json-syntax.ts +83 -0
  42. package/src/validators/paths.ts +140 -0
  43. package/src/validators/server-health.ts +165 -0
  44. package/tsconfig.json +18 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-vars.js","sourceRoot":"","sources":["../../src/validators/env-vars.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,UAAU,eAAe,CAC7B,MAA+B,EAC/B,UAAkB;IAElB,MAAM,OAAO,GAAuB,EAAE,CAAC;IACvC,MAAM,OAAO,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAE7C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACxD,MAAM,MAAM,GAAG,SAA4B,CAAC;QAE5C,IAAI,CAAC,MAAM,CAAC,GAAG;YAAE,SAAS;QAE1B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YACtD,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,SAAS;YAExC,sCAAsC;YACtC,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,gDAAgD,CAAC,IAAI,EAAE,CAAC;YAEpF,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;gBAC1B,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;gBAEhD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC1B,OAAO,CAAC,IAAI,CAAC;wBACX,KAAK,EAAE,SAAS;wBAChB,IAAI,EAAE,iBAAiB;wBACvB,OAAO,EAAE,WAAW,IAAI,2BAA2B,OAAO,aAAa;wBACvE,IAAI,EAAE,UAAU;wBAChB,UAAU,EAAE,uBAAuB,OAAO,eAAe;qBAC1D,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAED,2DAA2D;YAC3D,IAAI,KAAK,KAAK,EAAE,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACrD,OAAO,CAAC,IAAI,CAAC;oBACX,KAAK,EAAE,SAAS;oBAChB,IAAI,EAAE,eAAe;oBACrB,OAAO,EAAE,WAAW,IAAI,2BAA2B,GAAG,WAAW;oBACjE,IAAI,EAAE,UAAU;oBAChB,UAAU,EAAE,uDAAuD;iBACpE,CAAC,CAAC;YACL,CAAC;YAED,+BAA+B;YAC/B,MAAM,iBAAiB,GAAG;gBACxB,2DAA2D;gBAC3D,4DAA4D;aAC7D,CAAC;YAEF,MAAM,mBAAmB,GAAG;gBAC1B,0BAA0B,EAAY,oBAAoB;gBAC1D,kBAAkB,EAAqB,0BAA0B;gBACjE,oCAAoC,EAAE,aAAa;gBACnD,uBAAuB,EAAgB,gBAAgB;gBACvD,+BAA+B,EAAQ,aAAa;gBACpD,kBAAkB,EAAqB,mBAAmB;gBAC1D,kBAAkB,EAAqB,oBAAoB;gBAC3D,oBAAoB,EAAmB,kBAAkB;aAC1D,CAAC;YAEF,MAAM,WAAW,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAC/D,MAAM,eAAe,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YAEvE,6CAA6C;YAC7C,IAAI,CAAC,WAAW,IAAI,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC7D,OAAO,CAAC,IAAI,CAAC;oBACX,KAAK,EAAE,SAAS;oBAChB,IAAI,EAAE,kBAAkB;oBACxB,OAAO,EAAE,WAAW,IAAI,mCAAmC,GAAG,EAAE;oBAChE,IAAI,EAAE,UAAU;oBAChB,UAAU,EAAE,+CAA+C,GAAG,GAAG,CAAC,WAAW,EAAE,GAAG,WAAW;iBAC9F,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { validateJsonSyntax } from './json-syntax.js';
2
+ export { validatePaths } from './paths.js';
3
+ export { validateEnvVars } from './env-vars.js';
4
+ export { testServerHealth } from './server-health.js';
@@ -0,0 +1,5 @@
1
+ export { validateJsonSyntax } from './json-syntax.js';
2
+ export { validatePaths } from './paths.js';
3
+ export { validateEnvVars } from './env-vars.js';
4
+ export { testServerHealth } from './server-health.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/validators/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { ValidationResult } from '../config/types.js';
2
+ export declare function validateJsonSyntax(filePath: string): {
3
+ valid: boolean;
4
+ results: ValidationResult[];
5
+ config?: Record<string, unknown>;
6
+ };
@@ -0,0 +1,77 @@
1
+ import { readFileSync } from 'fs';
2
+ export function validateJsonSyntax(filePath) {
3
+ const results = [];
4
+ let content;
5
+ try {
6
+ content = readFileSync(filePath, 'utf-8');
7
+ }
8
+ catch (error) {
9
+ results.push({
10
+ level: 'error',
11
+ code: 'FILE_READ_ERROR',
12
+ message: `Cannot read file: ${error instanceof Error ? error.message : 'Unknown error'}`,
13
+ file: filePath,
14
+ });
15
+ return { valid: false, results };
16
+ }
17
+ // Check for empty file
18
+ if (!content.trim()) {
19
+ results.push({
20
+ level: 'warning',
21
+ code: 'EMPTY_FILE',
22
+ message: 'Config file is empty',
23
+ file: filePath,
24
+ });
25
+ return { valid: true, results, config: {} };
26
+ }
27
+ // Try to parse JSON
28
+ try {
29
+ const config = JSON.parse(content);
30
+ return { valid: true, results, config };
31
+ }
32
+ catch (error) {
33
+ if (error instanceof SyntaxError) {
34
+ // Try to find the line number
35
+ const match = error.message.match(/position\s+(\d+)/i);
36
+ let line;
37
+ let column;
38
+ if (match) {
39
+ const position = parseInt(match[1], 10);
40
+ const lines = content.substring(0, position).split('\n');
41
+ line = lines.length;
42
+ column = lines[lines.length - 1].length + 1;
43
+ }
44
+ // Check for common issues
45
+ const trailingCommaMatch = content.match(/,\s*([}\]])/);
46
+ if (trailingCommaMatch) {
47
+ // Find line of trailing comma
48
+ const beforeMatch = content.substring(0, content.indexOf(trailingCommaMatch[0]));
49
+ const trailingCommaLine = beforeMatch.split('\n').length;
50
+ results.push({
51
+ level: 'error',
52
+ code: 'JSON_TRAILING_COMMA',
53
+ message: 'Trailing comma detected (not allowed in JSON)',
54
+ file: filePath,
55
+ line: trailingCommaLine,
56
+ suggestion: 'Remove the comma before the closing bracket/brace',
57
+ });
58
+ }
59
+ else {
60
+ // Generic JSON error
61
+ let message = error.message;
62
+ if (line && column) {
63
+ message = `Invalid JSON at line ${line}, column ${column}: ${error.message}`;
64
+ }
65
+ results.push({
66
+ level: 'error',
67
+ code: 'JSON_SYNTAX',
68
+ message,
69
+ file: filePath,
70
+ line,
71
+ });
72
+ }
73
+ }
74
+ return { valid: false, results };
75
+ }
76
+ }
77
+ //# sourceMappingURL=json-syntax.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-syntax.js","sourceRoot":"","sources":["../../src/validators/json-syntax.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { ValidationResult } from '../config/types.js';
2
+ export declare function validatePaths(config: Record<string, unknown>, configPath: string): ValidationResult[];
@@ -0,0 +1,125 @@
1
+ import { existsSync, accessSync, constants } from 'fs';
2
+ import { isAbsolute, resolve, dirname } from 'path';
3
+ import { getServersFromConfig } from '../config/locations.js';
4
+ export function validatePaths(config, configPath) {
5
+ const results = [];
6
+ const servers = getServersFromConfig(config);
7
+ if (!servers) {
8
+ return results;
9
+ }
10
+ const configDir = dirname(configPath);
11
+ for (const [name, serverRaw] of Object.entries(servers)) {
12
+ const server = serverRaw;
13
+ // Skip HTTP/URL-based servers
14
+ if (server.url)
15
+ continue;
16
+ // Skip if no command specified
17
+ if (!server.command) {
18
+ results.push({
19
+ level: 'error',
20
+ code: 'MISSING_COMMAND',
21
+ message: `Server "${name}": No command specified`,
22
+ file: configPath,
23
+ suggestion: 'Add a "command" field specifying how to start the server',
24
+ });
25
+ continue;
26
+ }
27
+ const command = server.command;
28
+ // Check if command is a path (contains / or \)
29
+ if (command.includes('/') || command.includes('\\')) {
30
+ const resolvedPath = isAbsolute(command)
31
+ ? command
32
+ : resolve(configDir, command);
33
+ // Check existence
34
+ if (!existsSync(resolvedPath)) {
35
+ results.push({
36
+ level: 'error',
37
+ code: 'PATH_NOT_FOUND',
38
+ message: `Server "${name}": Command path does not exist: ${command}`,
39
+ file: configPath,
40
+ suggestion: `Check the path is correct. Resolved to: ${resolvedPath}`,
41
+ });
42
+ }
43
+ else {
44
+ // Check executable permission (Unix only)
45
+ if (process.platform !== 'win32') {
46
+ try {
47
+ accessSync(resolvedPath, constants.X_OK);
48
+ }
49
+ catch {
50
+ results.push({
51
+ level: 'error',
52
+ code: 'PATH_NOT_EXECUTABLE',
53
+ message: `Server "${name}": Command is not executable: ${command}`,
54
+ file: configPath,
55
+ suggestion: `Run: chmod +x "${resolvedPath}"`,
56
+ });
57
+ }
58
+ }
59
+ }
60
+ // Warn about relative paths
61
+ if (!isAbsolute(command)) {
62
+ results.push({
63
+ level: 'warning',
64
+ code: 'PATH_RELATIVE',
65
+ message: `Server "${name}": Using relative path "${command}"`,
66
+ file: configPath,
67
+ suggestion: `Consider using absolute path: ${resolvedPath}`,
68
+ });
69
+ }
70
+ }
71
+ else {
72
+ // Command is not a path (e.g., "npx", "node", "python")
73
+ // We can't easily validate these as they depend on PATH
74
+ // But we can warn if it looks like a typo
75
+ const commonCommands = ['npx', 'node', 'python', 'python3', 'uv', 'uvx', 'deno', 'bun'];
76
+ const looksLikePath = command.includes('.') && !commonCommands.includes(command);
77
+ if (looksLikePath) {
78
+ results.push({
79
+ level: 'warning',
80
+ code: 'POSSIBLE_PATH_TYPO',
81
+ message: `Server "${name}": "${command}" looks like a filename but isn't a path`,
82
+ file: configPath,
83
+ suggestion: 'If this is a file, use a full path like "./server.js" or "/path/to/server.js"',
84
+ });
85
+ }
86
+ }
87
+ // Check cwd if specified
88
+ if (server.cwd) {
89
+ const cwdPath = isAbsolute(server.cwd)
90
+ ? server.cwd
91
+ : resolve(configDir, server.cwd);
92
+ if (!existsSync(cwdPath)) {
93
+ results.push({
94
+ level: 'error',
95
+ code: 'CWD_NOT_FOUND',
96
+ message: `Server "${name}": Working directory does not exist: ${server.cwd}`,
97
+ file: configPath,
98
+ suggestion: `Create the directory or update the path. Resolved to: ${cwdPath}`,
99
+ });
100
+ }
101
+ }
102
+ // Check args for paths
103
+ if (server.args && Array.isArray(server.args)) {
104
+ for (const arg of server.args) {
105
+ // If arg looks like an absolute path, check it exists
106
+ if (typeof arg === 'string' && isAbsolute(arg) && (arg.includes('/') || arg.includes('\\'))) {
107
+ // Only check if it looks like a file path (not a URL or flag)
108
+ if (!arg.startsWith('-') && !arg.startsWith('http')) {
109
+ if (!existsSync(arg)) {
110
+ results.push({
111
+ level: 'warning',
112
+ code: 'ARG_PATH_NOT_FOUND',
113
+ message: `Server "${name}": Argument path may not exist: ${arg}`,
114
+ file: configPath,
115
+ suggestion: 'Verify this path is correct',
116
+ });
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ }
123
+ return results;
124
+ }
125
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/validators/paths.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { ServerTestResult } from '../config/types.js';
2
+ export declare function testServerHealth(config: Record<string, unknown>, timeout?: number): Promise<ServerTestResult[]>;
@@ -0,0 +1,135 @@
1
+ import { spawn } from 'child_process';
2
+ import { getServersFromConfig } from '../config/locations.js';
3
+ export async function testServerHealth(config, timeout = 10000) {
4
+ const results = [];
5
+ const servers = getServersFromConfig(config);
6
+ if (!servers) {
7
+ return results;
8
+ }
9
+ const testPromises = Object.entries(servers).map(async ([name, serverRaw]) => {
10
+ const server = serverRaw;
11
+ const startTime = Date.now();
12
+ // Skip URL-based servers for now
13
+ if (server.url) {
14
+ return {
15
+ name,
16
+ healthy: true, // Assume healthy for HTTP servers
17
+ responseTime: undefined,
18
+ error: 'HTTP servers not tested (coming soon)',
19
+ };
20
+ }
21
+ // Skip if no command
22
+ if (!server.command) {
23
+ return {
24
+ name,
25
+ healthy: false,
26
+ error: 'No command specified',
27
+ };
28
+ }
29
+ try {
30
+ const healthy = await testStdioServer(server.command, server.args || [], server.env, timeout);
31
+ const responseTime = Date.now() - startTime;
32
+ return {
33
+ name,
34
+ healthy,
35
+ responseTime: healthy ? responseTime : undefined,
36
+ error: healthy ? undefined : `Timeout after ${timeout}ms`,
37
+ };
38
+ }
39
+ catch (error) {
40
+ return {
41
+ name,
42
+ healthy: false,
43
+ error: error instanceof Error ? error.message : String(error),
44
+ };
45
+ }
46
+ });
47
+ return Promise.all(testPromises);
48
+ }
49
+ async function testStdioServer(command, args, env, timeout = 10000) {
50
+ return new Promise((resolve) => {
51
+ let proc;
52
+ try {
53
+ proc = spawn(command, args, {
54
+ stdio: ['pipe', 'pipe', 'pipe'],
55
+ env: { ...process.env, ...env },
56
+ shell: process.platform === 'win32', // Use shell on Windows for npx etc
57
+ });
58
+ }
59
+ catch (error) {
60
+ resolve(false);
61
+ return;
62
+ }
63
+ const timer = setTimeout(() => {
64
+ proc.kill();
65
+ resolve(false);
66
+ }, timeout);
67
+ let buffer = '';
68
+ let initialized = false;
69
+ proc.stdout?.on('data', (data) => {
70
+ buffer += data.toString();
71
+ // Try to parse complete JSON-RPC messages
72
+ const lines = buffer.split('\n');
73
+ buffer = lines.pop() || '';
74
+ for (const line of lines) {
75
+ if (!line.trim())
76
+ continue;
77
+ try {
78
+ const message = JSON.parse(line);
79
+ // Handle initialize response
80
+ if (message.id === 1 && message.result && !initialized) {
81
+ initialized = true;
82
+ clearTimeout(timer);
83
+ proc.kill();
84
+ resolve(true);
85
+ }
86
+ // Handle error response
87
+ if (message.id === 1 && message.error) {
88
+ clearTimeout(timer);
89
+ proc.kill();
90
+ resolve(false);
91
+ }
92
+ }
93
+ catch {
94
+ // Not valid JSON, might be partial or non-JSON output
95
+ }
96
+ }
97
+ });
98
+ proc.on('error', (error) => {
99
+ clearTimeout(timer);
100
+ // Check for common issues
101
+ if (error.code === 'ENOENT') {
102
+ // Command not found - this is expected for some setups
103
+ }
104
+ resolve(false);
105
+ });
106
+ proc.on('exit', (code) => {
107
+ if (!initialized) {
108
+ clearTimeout(timer);
109
+ // Non-zero exit before responding is a failure
110
+ if (code !== 0 && code !== null) {
111
+ resolve(false);
112
+ }
113
+ }
114
+ });
115
+ // Send initialize request
116
+ const initRequest = JSON.stringify({
117
+ jsonrpc: '2.0',
118
+ id: 1,
119
+ method: 'initialize',
120
+ params: {
121
+ protocolVersion: '2024-11-05',
122
+ capabilities: {},
123
+ clientInfo: { name: 'mcp-doctor', version: '1.0.0' },
124
+ },
125
+ });
126
+ try {
127
+ proc.stdin?.write(initRequest + '\n');
128
+ }
129
+ catch {
130
+ clearTimeout(timer);
131
+ resolve(false);
132
+ }
133
+ });
134
+ }
135
+ //# sourceMappingURL=server-health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-health.js","sourceRoot":"","sources":["../../src/validators/server-health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAEtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAA+B,EAC/B,OAAO,GAAG,KAAK;IAEf,MAAM,OAAO,GAAuB,EAAE,CAAC;IACvC,MAAM,OAAO,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAE7C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,EAAE;QAC3E,MAAM,MAAM,GAAG,SAA4B,CAAC;QAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE7B,iCAAiC;QACjC,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;YACf,OAAO;gBACL,IAAI;gBACJ,OAAO,EAAE,IAAI,EAAE,kCAAkC;gBACjD,YAAY,EAAE,SAAS;gBACvB,KAAK,EAAE,uCAAuC;aAC/C,CAAC;QACJ,CAAC;QAED,qBAAqB;QACrB,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO;gBACL,IAAI;gBACJ,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,sBAAsB;aAC9B,CAAC;QACJ,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,eAAe,CACnC,MAAM,CAAC,OAAO,EACd,MAAM,CAAC,IAAI,IAAI,EAAE,EACjB,MAAM,CAAC,GAAG,EACV,OAAO,CACR,CAAC;YACF,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAE5C,OAAO;gBACL,IAAI;gBACJ,OAAO;gBACP,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;gBAChD,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,OAAO,IAAI;aAC1D,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,IAAI;gBACJ,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AACnC,CAAC;AAED,KAAK,UAAU,eAAe,CAC5B,OAAe,EACf,IAAc,EACd,GAA4B,EAC5B,OAAO,GAAG,KAAK;IAEf,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,IAA8B,CAAC;QAEnC,IAAI,CAAC;YACH,IAAI,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE;gBAC1B,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;gBAC/B,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,GAAG,EAAE;gBAC/B,KAAK,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,mCAAmC;aACzE,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,CAAC;YACf,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;YACZ,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,EAAE,OAAO,CAAC,CAAC;QAEZ,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,WAAW,GAAG,KAAK,CAAC;QAExB,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YAC/B,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAE1B,0CAA0C;YAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACjC,MAAM,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;YAE3B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;oBAAE,SAAS;gBAE3B,IAAI,CAAC;oBACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBAEjC,6BAA6B;oBAC7B,IAAI,OAAO,CAAC,EAAE,KAAK,CAAC,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;wBACvD,WAAW,GAAG,IAAI,CAAC;wBACnB,YAAY,CAAC,KAAK,CAAC,CAAC;wBACpB,IAAI,CAAC,IAAI,EAAE,CAAC;wBACZ,OAAO,CAAC,IAAI,CAAC,CAAC;oBAChB,CAAC;oBAED,wBAAwB;oBACxB,IAAI,OAAO,CAAC,EAAE,KAAK,CAAC,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;wBACtC,YAAY,CAAC,KAAK,CAAC,CAAC;wBACpB,IAAI,CAAC,IAAI,EAAE,CAAC;wBACZ,OAAO,CAAC,KAAK,CAAC,CAAC;oBACjB,CAAC;gBACH,CAAC;gBAAC,MAAM,CAAC;oBACP,sDAAsD;gBACxD,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACzB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,0BAA0B;YAC1B,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvD,uDAAuD;YACzD,CAAC;YACD,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACvB,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,+CAA+C;gBAC/C,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;oBAChC,OAAO,CAAC,KAAK,CAAC,CAAC;gBACjB,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,0BAA0B;QAC1B,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC;YACjC,OAAO,EAAE,KAAK;YACd,EAAE,EAAE,CAAC;YACL,MAAM,EAAE,YAAY;YACpB,MAAM,EAAE;gBACN,eAAe,EAAE,YAAY;gBAC7B,YAAY,EAAE,EAAE;gBAChB,UAAU,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE;aACrD;SACF,CAAC,CAAC;QAEH,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "mcp-doctor",
3
+ "version": "0.1.0",
4
+ "description": "Diagnose and fix MCP configuration issues across Claude Desktop, Cursor, VS Code, and more",
5
+ "type": "module",
6
+ "bin": {
7
+ "mcp-doctor": "./dist/cli/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsx src/cli/index.ts",
12
+ "start": "node dist/cli/index.js",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "model-context-protocol",
18
+ "claude",
19
+ "cursor",
20
+ "vscode",
21
+ "debugging",
22
+ "cli",
23
+ "developer-tools",
24
+ "anthropic"
25
+ ],
26
+ "author": "",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/Crooj026/mcp-doctor"
31
+ },
32
+ "engines": {
33
+ "node": ">=18"
34
+ },
35
+ "dependencies": {
36
+ "chalk": "^5.3.0",
37
+ "commander": "^12.1.0",
38
+ "ora": "^8.0.1"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^20.10.0",
42
+ "tsx": "^4.7.0",
43
+ "typescript": "^5.3.0"
44
+ }
45
+ }
@@ -0,0 +1,103 @@
1
+ import ora from 'ora';
2
+ import { getConfigLocations } from '../../config/locations.js';
3
+ import {
4
+ validateJsonSyntax,
5
+ validatePaths,
6
+ validateEnvVars,
7
+ testServerHealth,
8
+ } from '../../validators/index.js';
9
+ import {
10
+ printHeader,
11
+ printConfigsFound,
12
+ printValidationResults,
13
+ printServerResults,
14
+ printSummary,
15
+ } from '../../utils/output.js';
16
+ import { ValidationResult, ServerTestResult } from '../../config/types.js';
17
+
18
+ interface CheckOptions {
19
+ skipHealth?: boolean;
20
+ }
21
+
22
+ export async function checkCommand(options: CheckOptions = {}): Promise<void> {
23
+ printHeader('🩺 MCP Doctor');
24
+
25
+ const spinner = ora('Scanning for MCP configurations...').start();
26
+
27
+ // Find all config files
28
+ const locations = getConfigLocations();
29
+ const foundConfigs = locations.filter((l) => l.exists);
30
+
31
+ spinner.stop();
32
+
33
+ // Show what we found
34
+ printConfigsFound(locations);
35
+
36
+ if (foundConfigs.length === 0) {
37
+ process.exitCode = 0;
38
+ return;
39
+ }
40
+
41
+ // Run validators on each config
42
+ const allResults: ValidationResult[] = [];
43
+ const allServerResults: ServerTestResult[] = [];
44
+ const configsWithServers: Array<{ path: string; config: Record<string, unknown> }> = [];
45
+
46
+ for (const configLoc of foundConfigs) {
47
+ // JSON syntax check
48
+ const { valid, results: syntaxResults, config } = validateJsonSyntax(configLoc.path);
49
+ allResults.push(...syntaxResults);
50
+
51
+ // If JSON is invalid, skip other validators for this file
52
+ if (!valid || !config) {
53
+ continue;
54
+ }
55
+
56
+ // Store for server health testing
57
+ configsWithServers.push({ path: configLoc.path, config });
58
+
59
+ // Path validation
60
+ allResults.push(...validatePaths(config, configLoc.path));
61
+
62
+ // Environment variable validation
63
+ allResults.push(...validateEnvVars(config, configLoc.path));
64
+ }
65
+
66
+ // Print validation results
67
+ printValidationResults(allResults);
68
+
69
+ // Server health tests (unless skipped)
70
+ if (!options.skipHealth && configsWithServers.length > 0) {
71
+ const healthSpinner = ora('Testing server connectivity...').start();
72
+
73
+ for (const { config } of configsWithServers) {
74
+ const results = await testServerHealth(config);
75
+ allServerResults.push(...results);
76
+ }
77
+
78
+ healthSpinner.stop();
79
+
80
+ // Deduplicate servers by name (same server might be in multiple configs)
81
+ const uniqueServers = new Map<string, ServerTestResult>();
82
+ for (const result of allServerResults) {
83
+ // Keep the healthy result if we have conflicting results
84
+ if (!uniqueServers.has(result.name) || result.healthy) {
85
+ uniqueServers.set(result.name, result);
86
+ }
87
+ }
88
+
89
+ printServerResults(Array.from(uniqueServers.values()));
90
+ }
91
+
92
+ // Summary
93
+ const errors = allResults.filter((r) => r.level === 'error').length;
94
+ const warnings = allResults.filter((r) => r.level === 'warning').length;
95
+ const healthyServers = allServerResults.filter((s) => s.healthy).length;
96
+
97
+ printSummary(errors, warnings, healthyServers, allServerResults.length);
98
+
99
+ // Set exit code
100
+ if (errors > 0) {
101
+ process.exitCode = 1;
102
+ }
103
+ }
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { checkCommand } from './commands/check.js';
5
+
6
+ const program = new Command();
7
+
8
+ program
9
+ .name('mcp-doctor')
10
+ .description('Diagnose and fix MCP configuration issues')
11
+ .version('0.1.0');
12
+
13
+ program
14
+ .command('check')
15
+ .description('Validate all MCP configuration files')
16
+ .option('--skip-health', 'Skip server health checks')
17
+ .action(async (options) => {
18
+ await checkCommand({
19
+ skipHealth: options.skipHealth,
20
+ });
21
+ });
22
+
23
+ // Default command (no subcommand) runs check
24
+ program
25
+ .action(async () => {
26
+ await checkCommand();
27
+ });
28
+
29
+ program.parse();