mcp-doctor 0.1.0 → 0.1.1

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 CHANGED
@@ -37,6 +37,14 @@ Found 2 config files:
37
37
 
38
38
  ~/.cursor/mcp.json:12
39
39
  Trailing comma detected (not allowed in JSON)
40
+
41
+ 10 │ "github": {
42
+ 11 │ "command": "npx",
43
+ > 12 │ "args": ["-y", "@modelcontextprotocol/server-github"],
44
+ ^
45
+ 13 │ },
46
+ 14 │ }
47
+
40
48
  💡 Remove the comma before the closing bracket/brace
41
49
 
42
50
  ⚠️ 2 Warnings
@@ -92,6 +100,10 @@ mcp-doctor check
92
100
 
93
101
  # Skip server health tests (faster)
94
102
  mcp-doctor check --skip-health
103
+
104
+ # Validate a specific config file
105
+ mcp-doctor --file ./my-config.json
106
+ mcp-doctor --file ~/custom/location/config.json
95
107
  ```
96
108
 
97
109
  ## Installation
@@ -106,6 +118,18 @@ npm install -g mcp-doctor
106
118
  npx mcp-doctor
107
119
  ```
108
120
 
121
+ ## Config File Locations
122
+
123
+ | Client | Location |
124
+ | ------------------------ | ----------------------------------------------------------------- |
125
+ | Claude Desktop (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
126
+ | Claude Desktop (Windows) | `%APPDATA%/Claude/claude_desktop_config.json` |
127
+ | Claude Desktop (Linux) | `~/.config/claude/claude_desktop_config.json` |
128
+ | Cursor | `~/.cursor/mcp.json` |
129
+ | VS Code | `~/Library/Application Support/Code/User/settings.json` |
130
+ | Claude Code | `~/.claude.json` or `./.mcp.json` |
131
+ | Windsurf | `~/.codeium/windsurf/mcp_config.json` |
132
+
109
133
  ## FAQ
110
134
 
111
135
  **Q: It says "No MCP configuration files found"**
@@ -1,5 +1,6 @@
1
1
  interface CheckOptions {
2
2
  skipHealth?: boolean;
3
+ filePath?: string;
3
4
  }
4
5
  export declare function checkCommand(options?: CheckOptions): Promise<void>;
5
6
  export {};
@@ -1,28 +1,56 @@
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';
1
+ import ora from "ora";
2
+ import { existsSync } from "fs";
3
+ import { resolve } from "path";
4
+ import { getConfigLocations } from "../../config/locations.js";
5
+ import { validateJsonSyntax, validatePaths, validateEnvVars, testServerHealth, } from "../../validators/index.js";
6
+ import { printHeader, printConfigsFound, printValidationResults, printServerResults, printSummary, } from "../../utils/output.js";
5
7
  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;
8
+ printHeader("🩺 MCP Doctor");
9
+ let foundConfigs;
10
+ // If --file is specified, use that instead of scanning
11
+ if (options.filePath) {
12
+ const resolvedPath = resolve(options.filePath);
13
+ if (!existsSync(resolvedPath)) {
14
+ console.log(`\n❌ File not found: ${resolvedPath}\n`);
15
+ process.exitCode = 1;
16
+ return;
17
+ }
18
+ foundConfigs = [
19
+ {
20
+ client: "Custom",
21
+ scope: "global",
22
+ path: resolvedPath,
23
+ exists: true,
24
+ },
25
+ ];
26
+ console.log(`Validating: ${resolvedPath}\n`);
27
+ }
28
+ else {
29
+ const spinner = ora("Scanning for MCP configurations...").start();
30
+ // Find all config files
31
+ const locations = getConfigLocations();
32
+ foundConfigs = locations.filter((l) => l.exists);
33
+ spinner.stop();
34
+ // Show what we found
35
+ printConfigsFound(locations);
36
+ if (foundConfigs.length === 0) {
37
+ process.exitCode = 0;
38
+ return;
39
+ }
17
40
  }
18
41
  // Run validators on each config
19
42
  const allResults = [];
20
43
  const allServerResults = [];
21
44
  const configsWithServers = [];
45
+ let firstErrorContext;
22
46
  for (const configLoc of foundConfigs) {
23
47
  // JSON syntax check
24
- const { valid, results: syntaxResults, config } = validateJsonSyntax(configLoc.path);
48
+ const { valid, results: syntaxResults, config, errorContext, } = validateJsonSyntax(configLoc.path);
25
49
  allResults.push(...syntaxResults);
50
+ // Capture first error context for display
51
+ if (errorContext && !firstErrorContext) {
52
+ firstErrorContext = errorContext;
53
+ }
26
54
  // If JSON is invalid, skip other validators for this file
27
55
  if (!valid || !config) {
28
56
  continue;
@@ -35,10 +63,10 @@ export async function checkCommand(options = {}) {
35
63
  allResults.push(...validateEnvVars(config, configLoc.path));
36
64
  }
37
65
  // Print validation results
38
- printValidationResults(allResults);
66
+ printValidationResults(allResults, firstErrorContext);
39
67
  // Server health tests (unless skipped)
40
68
  if (!options.skipHealth && configsWithServers.length > 0) {
41
- const healthSpinner = ora('Testing server connectivity...').start();
69
+ const healthSpinner = ora("Testing server connectivity...").start();
42
70
  for (const { config } of configsWithServers) {
43
71
  const results = await testServerHealth(config);
44
72
  allServerResults.push(...results);
@@ -55,8 +83,8 @@ export async function checkCommand(options = {}) {
55
83
  printServerResults(Array.from(uniqueServers.values()));
56
84
  }
57
85
  // Summary
58
- const errors = allResults.filter((r) => r.level === 'error').length;
59
- const warnings = allResults.filter((r) => r.level === 'warning').length;
86
+ const errors = allResults.filter((r) => r.level === "error").length;
87
+ const warnings = allResults.filter((r) => r.level === "warning").length;
60
88
  const healthyServers = allServerResults.filter((s) => s.healthy).length;
61
89
  printSummary(errors, warnings, healthyServers, allServerResults.length);
62
90
  // Set exit code
@@ -1 +1 @@
1
- {"version":3,"file":"check.js","sourceRoot":"","sources":["../../../src/cli/commands/check.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../../../src/cli/commands/check.ts"],"names":[],"mappings":"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"}
package/dist/cli/index.js CHANGED
@@ -1,24 +1,31 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from 'commander';
3
- import { checkCommand } from './commands/check.js';
2
+ import { Command } from "commander";
3
+ import { checkCommand } from "./commands/check.js";
4
4
  const program = new Command();
5
5
  program
6
- .name('mcp-doctor')
7
- .description('Diagnose and fix MCP configuration issues')
8
- .version('0.1.0');
6
+ .name("mcp-doctor")
7
+ .description("Diagnose and fix MCP configuration issues")
8
+ .version("0.1.1");
9
9
  program
10
- .command('check')
11
- .description('Validate all MCP configuration files')
12
- .option('--skip-health', 'Skip server health checks')
10
+ .command("check")
11
+ .description("Validate MCP configuration files")
12
+ .option("--skip-health", "Skip server health checks")
13
+ .option("--file <path>", "Validate a specific config file")
13
14
  .action(async (options) => {
14
15
  await checkCommand({
15
16
  skipHealth: options.skipHealth,
17
+ filePath: options.file,
16
18
  });
17
19
  });
18
20
  // Default command (no subcommand) runs check
19
21
  program
20
- .action(async () => {
21
- await checkCommand();
22
+ .option("--skip-health", "Skip server health checks")
23
+ .option("--file <path>", "Validate a specific config file")
24
+ .action(async (options) => {
25
+ await checkCommand({
26
+ skipHealth: options.skipHealth,
27
+ filePath: options.file,
28
+ });
22
29
  });
23
30
  program.parse();
24
31
  //# sourceMappingURL=index.js.map
@@ -1 +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"}
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,kCAAkC,CAAC;KAC/C,MAAM,CAAC,eAAe,EAAE,2BAA2B,CAAC;KACpD,MAAM,CAAC,eAAe,EAAE,iCAAiC,CAAC;KAC1D,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,YAAY,CAAC;QACjB,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,QAAQ,EAAE,OAAO,CAAC,IAAI;KACvB,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEL,6CAA6C;AAC7C,OAAO;KACJ,MAAM,CAAC,eAAe,EAAE,2BAA2B,CAAC;KACpD,MAAM,CAAC,eAAe,EAAE,iCAAiC,CAAC;KAC1D,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,YAAY,CAAC;QACjB,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,QAAQ,EAAE,OAAO,CAAC,IAAI;KACvB,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -1,6 +1,17 @@
1
- import { ValidationResult, ServerTestResult, ConfigLocation } from '../config/types.js';
1
+ import { ValidationResult, ServerTestResult, ConfigLocation } from "../config/types.js";
2
+ interface ErrorContext {
3
+ line: number;
4
+ column: number;
5
+ lineContent: string;
6
+ surroundingLines: Array<{
7
+ num: number;
8
+ content: string;
9
+ }>;
10
+ }
2
11
  export declare function printHeader(text: string): void;
3
12
  export declare function printConfigsFound(configs: ConfigLocation[]): void;
4
- export declare function printValidationResults(results: ValidationResult[]): void;
13
+ export declare function printCodeContext(errorContext: ErrorContext): void;
14
+ export declare function printValidationResults(results: ValidationResult[], errorContext?: ErrorContext): void;
5
15
  export declare function printServerResults(results: ServerTestResult[]): void;
6
16
  export declare function printSummary(errors: number, warnings: number, healthyServers: number, totalServers: number): void;
17
+ export {};
@@ -1,4 +1,4 @@
1
- import chalk from 'chalk';
1
+ import chalk from "chalk";
2
2
  export function printHeader(text) {
3
3
  console.log(chalk.bold(`\n${text}\n`));
4
4
  }
@@ -6,8 +6,8 @@ export function printConfigsFound(configs) {
6
6
  const found = configs.filter((c) => c.exists);
7
7
  const notFound = configs.filter((c) => !c.exists);
8
8
  if (found.length === 0) {
9
- console.log(chalk.yellow('No MCP configuration files found.\n'));
10
- console.log(chalk.gray('Looked in:'));
9
+ console.log(chalk.yellow("No MCP configuration files found.\n"));
10
+ console.log(chalk.gray("Looked in:"));
11
11
  notFound.slice(0, 5).forEach((c) => {
12
12
  console.log(chalk.gray(` ${c.client}: ${c.path}`));
13
13
  });
@@ -16,51 +16,78 @@ export function printConfigsFound(configs) {
16
16
  }
17
17
  return;
18
18
  }
19
- console.log(chalk.green(`Found ${found.length} config file${found.length > 1 ? 's' : ''}:\n`));
19
+ console.log(chalk.green(`Found ${found.length} config file${found.length > 1 ? "s" : ""}:\n`));
20
20
  found.forEach((c) => {
21
- const scope = c.scope === 'project' ? chalk.gray(' (project)') : '';
21
+ const scope = c.scope === "project" ? chalk.gray(" (project)") : "";
22
22
  console.log(` ${chalk.cyan(c.client)}${scope}`);
23
23
  console.log(chalk.gray(` ${c.path}\n`));
24
24
  });
25
25
  }
26
- export function printValidationResults(results) {
27
- const errors = results.filter((r) => r.level === 'error');
28
- const warnings = results.filter((r) => r.level === 'warning');
26
+ export function printCodeContext(errorContext) {
27
+ const { line: errorLine, column, surroundingLines } = errorContext;
28
+ const lineNumWidth = Math.max(...surroundingLines.map((l) => l.num.toString().length));
29
+ console.log("");
30
+ for (const { num, content } of surroundingLines) {
31
+ const lineNum = num.toString().padStart(lineNumWidth, " ");
32
+ const isErrorLine = num === errorLine;
33
+ if (isErrorLine) {
34
+ console.log(chalk.red(` > ${lineNum} │ ${content}`));
35
+ // Print the pointer
36
+ const pointerPadding = " ".repeat(4 + lineNumWidth + 3 + column - 1);
37
+ console.log(chalk.red(`${pointerPadding}^`));
38
+ }
39
+ else {
40
+ console.log(chalk.gray(` ${lineNum} │ ${content}`));
41
+ }
42
+ }
43
+ console.log("");
44
+ }
45
+ export function printValidationResults(results, errorContext) {
46
+ const errors = results.filter((r) => r.level === "error");
47
+ const warnings = results.filter((r) => r.level === "warning");
29
48
  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}` : '';
49
+ console.log(chalk.red(`❌ ${errors.length} Error${errors.length > 1 ? "s" : ""}\n`));
50
+ errors.forEach((e, index) => {
51
+ const location = e.line ? `:${e.line}` : "";
33
52
  console.log(chalk.red(` ${shortPath(e.file)}${location}`));
34
53
  console.log(chalk.red(` ${e.message}`));
54
+ // Show code context for first JSON error
55
+ if (errorContext &&
56
+ index === 0 &&
57
+ (e.code === "JSON_TRAILING_COMMA" || e.code === "JSON_SYNTAX")) {
58
+ printCodeContext(errorContext);
59
+ }
35
60
  if (e.suggestion) {
36
61
  console.log(chalk.gray(` 💡 ${e.suggestion}`));
37
62
  }
38
- console.log('');
63
+ console.log("");
39
64
  });
40
65
  }
41
66
  if (warnings.length > 0) {
42
- console.log(chalk.yellow(`⚠️ ${warnings.length} Warning${warnings.length > 1 ? 's' : ''}\n`));
67
+ console.log(chalk.yellow(`⚠️ ${warnings.length} Warning${warnings.length > 1 ? "s" : ""}\n`));
43
68
  warnings.forEach((w) => {
44
- const location = w.line ? `:${w.line}` : '';
69
+ const location = w.line ? `:${w.line}` : "";
45
70
  console.log(chalk.yellow(` ${shortPath(w.file)}${location}`));
46
71
  console.log(chalk.yellow(` ${w.message}`));
47
72
  if (w.suggestion) {
48
73
  console.log(chalk.gray(` 💡 ${w.suggestion}`));
49
74
  }
50
- console.log('');
75
+ console.log("");
51
76
  });
52
77
  }
53
78
  }
54
79
  export function printServerResults(results) {
55
80
  if (results.length === 0) {
56
- console.log(chalk.gray('No servers to test.\n'));
81
+ console.log(chalk.gray("No servers to test.\n"));
57
82
  return;
58
83
  }
59
84
  const healthy = results.filter((s) => s.healthy);
60
85
  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`);
86
+ console.log(`🔌 Server Health: ${chalk.green(healthy.length)} healthy, ${unhealthy.length > 0
87
+ ? chalk.red(unhealthy.length + " failed")
88
+ : chalk.gray("0 failed")}\n`);
62
89
  healthy.forEach((s) => {
63
- const time = s.responseTime ? chalk.gray(` (${s.responseTime}ms)`) : '';
90
+ const time = s.responseTime ? chalk.gray(` (${s.responseTime}ms)`) : "";
64
91
  console.log(chalk.green(` ✓ ${s.name}${time}`));
65
92
  });
66
93
  unhealthy.forEach((s) => {
@@ -69,33 +96,33 @@ export function printServerResults(results) {
69
96
  console.log(chalk.gray(` ${s.error}`));
70
97
  }
71
98
  });
72
- console.log('');
99
+ console.log("");
73
100
  }
74
101
  export function printSummary(errors, warnings, healthyServers, totalServers) {
75
- console.log(chalk.gray(''.repeat(50)));
102
+ console.log(chalk.gray("".repeat(50)));
76
103
  if (errors === 0 && warnings === 0) {
77
- console.log(chalk.green('\n✓ All configurations valid'));
104
+ console.log(chalk.green("\n✓ All configurations valid"));
78
105
  }
79
106
  else if (errors === 0) {
80
- console.log(chalk.yellow(`\n⚠️ Configuration valid with ${warnings} warning${warnings > 1 ? 's' : ''}`));
107
+ console.log(chalk.yellow(`\n⚠️ Configuration valid with ${warnings} warning${warnings > 1 ? "s" : ""}`));
81
108
  }
82
109
  else {
83
- console.log(chalk.red(`\n❌ Found ${errors} error${errors > 1 ? 's' : ''} that need${errors === 1 ? 's' : ''} fixing`));
110
+ console.log(chalk.red(`\n❌ Found ${errors} error${errors > 1 ? "s" : ""} that need${errors === 1 ? "s" : ""} fixing`));
84
111
  }
85
112
  if (totalServers > 0) {
86
113
  if (healthyServers === totalServers) {
87
- console.log(chalk.green(`✓ All ${totalServers} server${totalServers > 1 ? 's' : ''} responding`));
114
+ console.log(chalk.green(`✓ All ${totalServers} server${totalServers > 1 ? "s" : ""} responding`));
88
115
  }
89
116
  else {
90
117
  console.log(chalk.yellow(`⚠️ ${healthyServers}/${totalServers} servers responding`));
91
118
  }
92
119
  }
93
- console.log('');
120
+ console.log("");
94
121
  }
95
122
  function shortPath(path) {
96
- const home = process.env.HOME || process.env.USERPROFILE || '';
123
+ const home = process.env.HOME || process.env.USERPROFILE || "";
97
124
  if (home && path.startsWith(home)) {
98
- return '~' + path.slice(home.length);
125
+ return "~" + path.slice(home.length);
99
126
  }
100
127
  return path;
101
128
  }
@@ -1 +1 @@
1
- {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/utils/output.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/utils/output.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,16 @@
1
- import { ValidationResult } from '../config/types.js';
2
- export declare function validateJsonSyntax(filePath: string): {
1
+ import { ValidationResult } from "../config/types.js";
2
+ export interface JsonSyntaxResult {
3
3
  valid: boolean;
4
4
  results: ValidationResult[];
5
5
  config?: Record<string, unknown>;
6
- };
6
+ errorContext?: {
7
+ line: number;
8
+ column: number;
9
+ lineContent: string;
10
+ surroundingLines: Array<{
11
+ num: number;
12
+ content: string;
13
+ }>;
14
+ };
15
+ }
16
+ export declare function validateJsonSyntax(filePath: string): JsonSyntaxResult;
@@ -1,15 +1,15 @@
1
- import { readFileSync } from 'fs';
1
+ import { readFileSync } from "fs";
2
2
  export function validateJsonSyntax(filePath) {
3
3
  const results = [];
4
4
  let content;
5
5
  try {
6
- content = readFileSync(filePath, 'utf-8');
6
+ content = readFileSync(filePath, "utf-8");
7
7
  }
8
8
  catch (error) {
9
9
  results.push({
10
- level: 'error',
11
- code: 'FILE_READ_ERROR',
12
- message: `Cannot read file: ${error instanceof Error ? error.message : 'Unknown error'}`,
10
+ level: "error",
11
+ code: "FILE_READ_ERROR",
12
+ message: `Cannot read file: ${error instanceof Error ? error.message : "Unknown error"}`,
13
13
  file: filePath,
14
14
  });
15
15
  return { valid: false, results };
@@ -17,13 +17,14 @@ export function validateJsonSyntax(filePath) {
17
17
  // Check for empty file
18
18
  if (!content.trim()) {
19
19
  results.push({
20
- level: 'warning',
21
- code: 'EMPTY_FILE',
22
- message: 'Config file is empty',
20
+ level: "warning",
21
+ code: "EMPTY_FILE",
22
+ message: "Config file is empty",
23
23
  file: filePath,
24
24
  });
25
25
  return { valid: true, results, config: {} };
26
26
  }
27
+ const lines = content.split("\n");
27
28
  // Try to parse JSON
28
29
  try {
29
30
  const config = JSON.parse(content);
@@ -31,45 +32,75 @@ export function validateJsonSyntax(filePath) {
31
32
  }
32
33
  catch (error) {
33
34
  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;
35
+ // Try to find the position from error message
36
+ const posMatch = error.message.match(/position\s+(\d+)/i);
37
+ let line = 1;
38
+ let column = 1;
39
+ if (posMatch) {
40
+ const position = parseInt(posMatch[1], 10);
41
+ // Convert position to line/column
42
+ let currentPos = 0;
43
+ for (let i = 0; i < lines.length; i++) {
44
+ if (currentPos + lines[i].length + 1 > position) {
45
+ line = i + 1;
46
+ column = position - currentPos + 1;
47
+ break;
48
+ }
49
+ currentPos += lines[i].length + 1; // +1 for newline
50
+ }
51
+ }
52
+ // Check for trailing comma specifically
53
+ const trailingCommaRegex = /,(\s*[\]\}])/g;
54
+ let trailingCommaMatch;
55
+ let trailingCommaLine = 0;
56
+ let trailingCommaColumn = 0;
57
+ let searchPos = 0;
58
+ while ((trailingCommaMatch = trailingCommaRegex.exec(content)) !== null) {
59
+ // Find line number of this match
60
+ const beforeMatch = content.substring(0, trailingCommaMatch.index);
61
+ const matchLines = beforeMatch.split("\n");
62
+ trailingCommaLine = matchLines.length;
63
+ trailingCommaColumn = matchLines[matchLines.length - 1].length + 1;
64
+ searchPos = trailingCommaMatch.index;
43
65
  }
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;
66
+ // Build surrounding lines context
67
+ const errorLine = trailingCommaLine > 0 ? trailingCommaLine : line;
68
+ const errorColumn = trailingCommaLine > 0 ? trailingCommaColumn : column;
69
+ const surroundingLines = [];
70
+ const startLine = Math.max(0, errorLine - 3);
71
+ const endLine = Math.min(lines.length - 1, errorLine + 1);
72
+ for (let i = startLine; i <= endLine; i++) {
73
+ surroundingLines.push({
74
+ num: i + 1,
75
+ content: lines[i],
76
+ });
77
+ }
78
+ const errorContext = {
79
+ line: errorLine,
80
+ column: errorColumn,
81
+ lineContent: lines[errorLine - 1] || "",
82
+ surroundingLines,
83
+ };
84
+ if (trailingCommaLine > 0) {
50
85
  results.push({
51
- level: 'error',
52
- code: 'JSON_TRAILING_COMMA',
53
- message: 'Trailing comma detected (not allowed in JSON)',
86
+ level: "error",
87
+ code: "JSON_TRAILING_COMMA",
88
+ message: "Trailing comma detected (not allowed in JSON)",
54
89
  file: filePath,
55
90
  line: trailingCommaLine,
56
- suggestion: 'Remove the comma before the closing bracket/brace',
91
+ suggestion: "Remove the comma before the closing bracket/brace",
57
92
  });
58
93
  }
59
94
  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
95
  results.push({
66
- level: 'error',
67
- code: 'JSON_SYNTAX',
68
- message,
96
+ level: "error",
97
+ code: "JSON_SYNTAX",
98
+ message: error.message,
69
99
  file: filePath,
70
100
  line,
71
101
  });
72
102
  }
103
+ return { valid: false, results, errorContext };
73
104
  }
74
105
  return { valid: false, results };
75
106
  }
@@ -1 +1 @@
1
- {"version":3,"file":"json-syntax.js","sourceRoot":"","sources":["../../src/validators/json-syntax.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"json-syntax.js","sourceRoot":"","sources":["../../src/validators/json-syntax.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-doctor",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Diagnose and fix MCP configuration issues across Claude Desktop, Cursor, VS Code, and more",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,53 +1,105 @@
1
- import ora from 'ora';
2
- import { getConfigLocations } from '../../config/locations.js';
1
+ import ora from "ora";
2
+ import { existsSync } from "fs";
3
+ import { resolve } from "path";
4
+ import { getConfigLocations } from "../../config/locations.js";
3
5
  import {
4
6
  validateJsonSyntax,
5
7
  validatePaths,
6
8
  validateEnvVars,
7
9
  testServerHealth,
8
- } from '../../validators/index.js';
10
+ } from "../../validators/index.js";
9
11
  import {
10
12
  printHeader,
11
13
  printConfigsFound,
12
14
  printValidationResults,
13
15
  printServerResults,
14
16
  printSummary,
15
- } from '../../utils/output.js';
16
- import { ValidationResult, ServerTestResult } from '../../config/types.js';
17
+ } from "../../utils/output.js";
18
+ import {
19
+ ValidationResult,
20
+ ServerTestResult,
21
+ ConfigLocation,
22
+ } from "../../config/types.js";
17
23
 
18
24
  interface CheckOptions {
19
25
  skipHealth?: boolean;
26
+ filePath?: string;
20
27
  }
21
28
 
22
29
  export async function checkCommand(options: CheckOptions = {}): Promise<void> {
23
- printHeader('🩺 MCP Doctor');
30
+ printHeader("🩺 MCP Doctor");
31
+
32
+ let foundConfigs: ConfigLocation[];
33
+
34
+ // If --file is specified, use that instead of scanning
35
+ if (options.filePath) {
36
+ const resolvedPath = resolve(options.filePath);
37
+
38
+ if (!existsSync(resolvedPath)) {
39
+ console.log(`\n❌ File not found: ${resolvedPath}\n`);
40
+ process.exitCode = 1;
41
+ return;
42
+ }
24
43
 
25
- const spinner = ora('Scanning for MCP configurations...').start();
44
+ foundConfigs = [
45
+ {
46
+ client: "Custom",
47
+ scope: "global",
48
+ path: resolvedPath,
49
+ exists: true,
50
+ },
51
+ ];
26
52
 
27
- // Find all config files
28
- const locations = getConfigLocations();
29
- const foundConfigs = locations.filter((l) => l.exists);
53
+ console.log(`Validating: ${resolvedPath}\n`);
54
+ } else {
55
+ const spinner = ora("Scanning for MCP configurations...").start();
30
56
 
31
- spinner.stop();
57
+ // Find all config files
58
+ const locations = getConfigLocations();
59
+ foundConfigs = locations.filter((l) => l.exists);
32
60
 
33
- // Show what we found
34
- printConfigsFound(locations);
61
+ spinner.stop();
35
62
 
36
- if (foundConfigs.length === 0) {
37
- process.exitCode = 0;
38
- return;
63
+ // Show what we found
64
+ printConfigsFound(locations);
65
+
66
+ if (foundConfigs.length === 0) {
67
+ process.exitCode = 0;
68
+ return;
69
+ }
39
70
  }
40
71
 
41
72
  // Run validators on each config
42
73
  const allResults: ValidationResult[] = [];
43
74
  const allServerResults: ServerTestResult[] = [];
44
- const configsWithServers: Array<{ path: string; config: Record<string, unknown> }> = [];
75
+ const configsWithServers: Array<{
76
+ path: string;
77
+ config: Record<string, unknown>;
78
+ }> = [];
79
+ let firstErrorContext:
80
+ | {
81
+ line: number;
82
+ column: number;
83
+ lineContent: string;
84
+ surroundingLines: Array<{ num: number; content: string }>;
85
+ }
86
+ | undefined;
45
87
 
46
88
  for (const configLoc of foundConfigs) {
47
89
  // JSON syntax check
48
- const { valid, results: syntaxResults, config } = validateJsonSyntax(configLoc.path);
90
+ const {
91
+ valid,
92
+ results: syntaxResults,
93
+ config,
94
+ errorContext,
95
+ } = validateJsonSyntax(configLoc.path);
49
96
  allResults.push(...syntaxResults);
50
97
 
98
+ // Capture first error context for display
99
+ if (errorContext && !firstErrorContext) {
100
+ firstErrorContext = errorContext;
101
+ }
102
+
51
103
  // If JSON is invalid, skip other validators for this file
52
104
  if (!valid || !config) {
53
105
  continue;
@@ -64,11 +116,11 @@ export async function checkCommand(options: CheckOptions = {}): Promise<void> {
64
116
  }
65
117
 
66
118
  // Print validation results
67
- printValidationResults(allResults);
119
+ printValidationResults(allResults, firstErrorContext);
68
120
 
69
121
  // Server health tests (unless skipped)
70
122
  if (!options.skipHealth && configsWithServers.length > 0) {
71
- const healthSpinner = ora('Testing server connectivity...').start();
123
+ const healthSpinner = ora("Testing server connectivity...").start();
72
124
 
73
125
  for (const { config } of configsWithServers) {
74
126
  const results = await testServerHealth(config);
@@ -90,8 +142,8 @@ export async function checkCommand(options: CheckOptions = {}): Promise<void> {
90
142
  }
91
143
 
92
144
  // Summary
93
- const errors = allResults.filter((r) => r.level === 'error').length;
94
- const warnings = allResults.filter((r) => r.level === 'warning').length;
145
+ const errors = allResults.filter((r) => r.level === "error").length;
146
+ const warnings = allResults.filter((r) => r.level === "warning").length;
95
147
  const healthyServers = allServerResults.filter((s) => s.healthy).length;
96
148
 
97
149
  printSummary(errors, warnings, healthyServers, allServerResults.length);
package/src/cli/index.ts CHANGED
@@ -1,29 +1,36 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { Command } from 'commander';
4
- import { checkCommand } from './commands/check.js';
3
+ import { Command } from "commander";
4
+ import { checkCommand } from "./commands/check.js";
5
5
 
6
6
  const program = new Command();
7
7
 
8
8
  program
9
- .name('mcp-doctor')
10
- .description('Diagnose and fix MCP configuration issues')
11
- .version('0.1.0');
9
+ .name("mcp-doctor")
10
+ .description("Diagnose and fix MCP configuration issues")
11
+ .version("0.1.1");
12
12
 
13
13
  program
14
- .command('check')
15
- .description('Validate all MCP configuration files')
16
- .option('--skip-health', 'Skip server health checks')
14
+ .command("check")
15
+ .description("Validate MCP configuration files")
16
+ .option("--skip-health", "Skip server health checks")
17
+ .option("--file <path>", "Validate a specific config file")
17
18
  .action(async (options) => {
18
19
  await checkCommand({
19
20
  skipHealth: options.skipHealth,
21
+ filePath: options.file,
20
22
  });
21
23
  });
22
24
 
23
25
  // Default command (no subcommand) runs check
24
26
  program
25
- .action(async () => {
26
- await checkCommand();
27
+ .option("--skip-health", "Skip server health checks")
28
+ .option("--file <path>", "Validate a specific config file")
29
+ .action(async (options) => {
30
+ await checkCommand({
31
+ skipHealth: options.skipHealth,
32
+ filePath: options.file,
33
+ });
27
34
  });
28
35
 
29
36
  program.parse();
@@ -1,5 +1,16 @@
1
- import chalk from 'chalk';
2
- import { ValidationResult, ServerTestResult, ConfigLocation } from '../config/types.js';
1
+ import chalk from "chalk";
2
+ import {
3
+ ValidationResult,
4
+ ServerTestResult,
5
+ ConfigLocation,
6
+ } from "../config/types.js";
7
+
8
+ interface ErrorContext {
9
+ line: number;
10
+ column: number;
11
+ lineContent: string;
12
+ surroundingLines: Array<{ num: number; content: string }>;
13
+ }
3
14
 
4
15
  export function printHeader(text: string): void {
5
16
  console.log(chalk.bold(`\n${text}\n`));
@@ -10,59 +21,109 @@ export function printConfigsFound(configs: ConfigLocation[]): void {
10
21
  const notFound = configs.filter((c) => !c.exists);
11
22
 
12
23
  if (found.length === 0) {
13
- console.log(chalk.yellow('No MCP configuration files found.\n'));
14
- console.log(chalk.gray('Looked in:'));
24
+ console.log(chalk.yellow("No MCP configuration files found.\n"));
25
+ console.log(chalk.gray("Looked in:"));
15
26
  notFound.slice(0, 5).forEach((c) => {
16
27
  console.log(chalk.gray(` ${c.client}: ${c.path}`));
17
28
  });
18
29
  if (notFound.length > 5) {
19
- console.log(chalk.gray(` ... and ${notFound.length - 5} more locations`));
30
+ console.log(
31
+ chalk.gray(` ... and ${notFound.length - 5} more locations`)
32
+ );
20
33
  }
21
34
  return;
22
35
  }
23
36
 
24
- console.log(chalk.green(`Found ${found.length} config file${found.length > 1 ? 's' : ''}:\n`));
37
+ console.log(
38
+ chalk.green(
39
+ `Found ${found.length} config file${found.length > 1 ? "s" : ""}:\n`
40
+ )
41
+ );
25
42
  found.forEach((c) => {
26
- const scope = c.scope === 'project' ? chalk.gray(' (project)') : '';
43
+ const scope = c.scope === "project" ? chalk.gray(" (project)") : "";
27
44
  console.log(` ${chalk.cyan(c.client)}${scope}`);
28
45
  console.log(chalk.gray(` ${c.path}\n`));
29
46
  });
30
47
  }
31
48
 
32
- export function printValidationResults(results: ValidationResult[]): void {
33
- const errors = results.filter((r) => r.level === 'error');
34
- const warnings = results.filter((r) => r.level === 'warning');
49
+ export function printCodeContext(errorContext: ErrorContext): void {
50
+ const { line: errorLine, column, surroundingLines } = errorContext;
51
+ const lineNumWidth = Math.max(
52
+ ...surroundingLines.map((l) => l.num.toString().length)
53
+ );
54
+
55
+ console.log("");
56
+
57
+ for (const { num, content } of surroundingLines) {
58
+ const lineNum = num.toString().padStart(lineNumWidth, " ");
59
+ const isErrorLine = num === errorLine;
60
+
61
+ if (isErrorLine) {
62
+ console.log(chalk.red(` > ${lineNum} │ ${content}`));
63
+ // Print the pointer
64
+ const pointerPadding = " ".repeat(4 + lineNumWidth + 3 + column - 1);
65
+ console.log(chalk.red(`${pointerPadding}^`));
66
+ } else {
67
+ console.log(chalk.gray(` ${lineNum} │ ${content}`));
68
+ }
69
+ }
70
+
71
+ console.log("");
72
+ }
73
+
74
+ export function printValidationResults(
75
+ results: ValidationResult[],
76
+ errorContext?: ErrorContext
77
+ ): void {
78
+ const errors = results.filter((r) => r.level === "error");
79
+ const warnings = results.filter((r) => r.level === "warning");
35
80
 
36
81
  if (errors.length > 0) {
37
- console.log(chalk.red(`❌ ${errors.length} Error${errors.length > 1 ? 's' : ''}\n`));
38
- errors.forEach((e) => {
39
- const location = e.line ? `:${e.line}` : '';
82
+ console.log(
83
+ chalk.red(`❌ ${errors.length} Error${errors.length > 1 ? "s" : ""}\n`)
84
+ );
85
+ errors.forEach((e, index) => {
86
+ const location = e.line ? `:${e.line}` : "";
40
87
  console.log(chalk.red(` ${shortPath(e.file)}${location}`));
41
88
  console.log(chalk.red(` ${e.message}`));
89
+
90
+ // Show code context for first JSON error
91
+ if (
92
+ errorContext &&
93
+ index === 0 &&
94
+ (e.code === "JSON_TRAILING_COMMA" || e.code === "JSON_SYNTAX")
95
+ ) {
96
+ printCodeContext(errorContext);
97
+ }
98
+
42
99
  if (e.suggestion) {
43
100
  console.log(chalk.gray(` 💡 ${e.suggestion}`));
44
101
  }
45
- console.log('');
102
+ console.log("");
46
103
  });
47
104
  }
48
105
 
49
106
  if (warnings.length > 0) {
50
- console.log(chalk.yellow(`⚠️ ${warnings.length} Warning${warnings.length > 1 ? 's' : ''}\n`));
107
+ console.log(
108
+ chalk.yellow(
109
+ `⚠️ ${warnings.length} Warning${warnings.length > 1 ? "s" : ""}\n`
110
+ )
111
+ );
51
112
  warnings.forEach((w) => {
52
- const location = w.line ? `:${w.line}` : '';
113
+ const location = w.line ? `:${w.line}` : "";
53
114
  console.log(chalk.yellow(` ${shortPath(w.file)}${location}`));
54
115
  console.log(chalk.yellow(` ${w.message}`));
55
116
  if (w.suggestion) {
56
117
  console.log(chalk.gray(` 💡 ${w.suggestion}`));
57
118
  }
58
- console.log('');
119
+ console.log("");
59
120
  });
60
121
  }
61
122
  }
62
123
 
63
124
  export function printServerResults(results: ServerTestResult[]): void {
64
125
  if (results.length === 0) {
65
- console.log(chalk.gray('No servers to test.\n'));
126
+ console.log(chalk.gray("No servers to test.\n"));
66
127
  return;
67
128
  }
68
129
 
@@ -71,12 +132,14 @@ export function printServerResults(results: ServerTestResult[]): void {
71
132
 
72
133
  console.log(
73
134
  `🔌 Server Health: ${chalk.green(healthy.length)} healthy, ${
74
- unhealthy.length > 0 ? chalk.red(unhealthy.length + ' failed') : chalk.gray('0 failed')
135
+ unhealthy.length > 0
136
+ ? chalk.red(unhealthy.length + " failed")
137
+ : chalk.gray("0 failed")
75
138
  }\n`
76
139
  );
77
140
 
78
141
  healthy.forEach((s) => {
79
- const time = s.responseTime ? chalk.gray(` (${s.responseTime}ms)`) : '';
142
+ const time = s.responseTime ? chalk.gray(` (${s.responseTime}ms)`) : "";
80
143
  console.log(chalk.green(` ✓ ${s.name}${time}`));
81
144
  });
82
145
 
@@ -87,7 +150,7 @@ export function printServerResults(results: ServerTestResult[]): void {
87
150
  }
88
151
  });
89
152
 
90
- console.log('');
153
+ console.log("");
91
154
  }
92
155
 
93
156
  export function printSummary(
@@ -96,19 +159,37 @@ export function printSummary(
96
159
  healthyServers: number,
97
160
  totalServers: number
98
161
  ): void {
99
- console.log(chalk.gray(''.repeat(50)));
162
+ console.log(chalk.gray("".repeat(50)));
100
163
 
101
164
  if (errors === 0 && warnings === 0) {
102
- console.log(chalk.green('\n✓ All configurations valid'));
165
+ console.log(chalk.green("\n✓ All configurations valid"));
103
166
  } else if (errors === 0) {
104
- console.log(chalk.yellow(`\n⚠️ Configuration valid with ${warnings} warning${warnings > 1 ? 's' : ''}`));
167
+ console.log(
168
+ chalk.yellow(
169
+ `\n⚠️ Configuration valid with ${warnings} warning${
170
+ warnings > 1 ? "s" : ""
171
+ }`
172
+ )
173
+ );
105
174
  } else {
106
- console.log(chalk.red(`\n❌ Found ${errors} error${errors > 1 ? 's' : ''} that need${errors === 1 ? 's' : ''} fixing`));
175
+ console.log(
176
+ chalk.red(
177
+ `\n❌ Found ${errors} error${errors > 1 ? "s" : ""} that need${
178
+ errors === 1 ? "s" : ""
179
+ } fixing`
180
+ )
181
+ );
107
182
  }
108
183
 
109
184
  if (totalServers > 0) {
110
185
  if (healthyServers === totalServers) {
111
- console.log(chalk.green(`✓ All ${totalServers} server${totalServers > 1 ? 's' : ''} responding`));
186
+ console.log(
187
+ chalk.green(
188
+ `✓ All ${totalServers} server${
189
+ totalServers > 1 ? "s" : ""
190
+ } responding`
191
+ )
192
+ );
112
193
  } else {
113
194
  console.log(
114
195
  chalk.yellow(`⚠️ ${healthyServers}/${totalServers} servers responding`)
@@ -116,13 +197,13 @@ export function printSummary(
116
197
  }
117
198
  }
118
199
 
119
- console.log('');
200
+ console.log("");
120
201
  }
121
202
 
122
203
  function shortPath(path: string): string {
123
- const home = process.env.HOME || process.env.USERPROFILE || '';
204
+ const home = process.env.HOME || process.env.USERPROFILE || "";
124
205
  if (home && path.startsWith(home)) {
125
- return '~' + path.slice(home.length);
206
+ return "~" + path.slice(home.length);
126
207
  }
127
208
  return path;
128
209
  }
@@ -1,17 +1,31 @@
1
- import { readFileSync } from 'fs';
2
- import { ValidationResult } from '../config/types.js';
1
+ import { readFileSync } from "fs";
2
+ import { ValidationResult } from "../config/types.js";
3
3
 
4
- export function validateJsonSyntax(filePath: string): { valid: boolean; results: ValidationResult[]; config?: Record<string, unknown> } {
4
+ export interface JsonSyntaxResult {
5
+ valid: boolean;
6
+ results: ValidationResult[];
7
+ config?: Record<string, unknown>;
8
+ errorContext?: {
9
+ line: number;
10
+ column: number;
11
+ lineContent: string;
12
+ surroundingLines: Array<{ num: number; content: string }>;
13
+ };
14
+ }
15
+
16
+ export function validateJsonSyntax(filePath: string): JsonSyntaxResult {
5
17
  const results: ValidationResult[] = [];
6
18
 
7
19
  let content: string;
8
20
  try {
9
- content = readFileSync(filePath, 'utf-8');
21
+ content = readFileSync(filePath, "utf-8");
10
22
  } catch (error) {
11
23
  results.push({
12
- level: 'error',
13
- code: 'FILE_READ_ERROR',
14
- message: `Cannot read file: ${error instanceof Error ? error.message : 'Unknown error'}`,
24
+ level: "error",
25
+ code: "FILE_READ_ERROR",
26
+ message: `Cannot read file: ${
27
+ error instanceof Error ? error.message : "Unknown error"
28
+ }`,
15
29
  file: filePath,
16
30
  });
17
31
  return { valid: false, results };
@@ -20,62 +34,99 @@ export function validateJsonSyntax(filePath: string): { valid: boolean; results:
20
34
  // Check for empty file
21
35
  if (!content.trim()) {
22
36
  results.push({
23
- level: 'warning',
24
- code: 'EMPTY_FILE',
25
- message: 'Config file is empty',
37
+ level: "warning",
38
+ code: "EMPTY_FILE",
39
+ message: "Config file is empty",
26
40
  file: filePath,
27
41
  });
28
42
  return { valid: true, results, config: {} };
29
43
  }
30
44
 
45
+ const lines = content.split("\n");
46
+
31
47
  // Try to parse JSON
32
48
  try {
33
49
  const config = JSON.parse(content);
34
50
  return { valid: true, results, config };
35
51
  } catch (error) {
36
52
  if (error instanceof SyntaxError) {
37
- // Try to find the line number
38
- const match = error.message.match(/position\s+(\d+)/i);
39
- let line: number | undefined;
40
- let column: number | undefined;
41
-
42
- if (match) {
43
- const position = parseInt(match[1], 10);
44
- const lines = content.substring(0, position).split('\n');
45
- line = lines.length;
46
- column = lines[lines.length - 1].length + 1;
53
+ // Try to find the position from error message
54
+ const posMatch = error.message.match(/position\s+(\d+)/i);
55
+ let line = 1;
56
+ let column = 1;
57
+
58
+ if (posMatch) {
59
+ const position = parseInt(posMatch[1], 10);
60
+ // Convert position to line/column
61
+ let currentPos = 0;
62
+ for (let i = 0; i < lines.length; i++) {
63
+ if (currentPos + lines[i].length + 1 > position) {
64
+ line = i + 1;
65
+ column = position - currentPos + 1;
66
+ break;
67
+ }
68
+ currentPos += lines[i].length + 1; // +1 for newline
69
+ }
70
+ }
71
+
72
+ // Check for trailing comma specifically
73
+ const trailingCommaRegex = /,(\s*[\]\}])/g;
74
+ let trailingCommaMatch;
75
+ let trailingCommaLine = 0;
76
+ let trailingCommaColumn = 0;
77
+ let searchPos = 0;
78
+
79
+ while ((trailingCommaMatch = trailingCommaRegex.exec(content)) !== null) {
80
+ // Find line number of this match
81
+ const beforeMatch = content.substring(0, trailingCommaMatch.index);
82
+ const matchLines = beforeMatch.split("\n");
83
+ trailingCommaLine = matchLines.length;
84
+ trailingCommaColumn = matchLines[matchLines.length - 1].length + 1;
85
+ searchPos = trailingCommaMatch.index;
47
86
  }
48
87
 
49
- // Check for common issues
50
- const trailingCommaMatch = content.match(/,\s*([}\]])/);
51
- if (trailingCommaMatch) {
52
- // Find line of trailing comma
53
- const beforeMatch = content.substring(0, content.indexOf(trailingCommaMatch[0]));
54
- const trailingCommaLine = beforeMatch.split('\n').length;
88
+ // Build surrounding lines context
89
+ const errorLine = trailingCommaLine > 0 ? trailingCommaLine : line;
90
+ const errorColumn = trailingCommaLine > 0 ? trailingCommaColumn : column;
91
+ const surroundingLines: Array<{ num: number; content: string }> = [];
55
92
 
93
+ const startLine = Math.max(0, errorLine - 3);
94
+ const endLine = Math.min(lines.length - 1, errorLine + 1);
95
+
96
+ for (let i = startLine; i <= endLine; i++) {
97
+ surroundingLines.push({
98
+ num: i + 1,
99
+ content: lines[i],
100
+ });
101
+ }
102
+
103
+ const errorContext = {
104
+ line: errorLine,
105
+ column: errorColumn,
106
+ lineContent: lines[errorLine - 1] || "",
107
+ surroundingLines,
108
+ };
109
+
110
+ if (trailingCommaLine > 0) {
56
111
  results.push({
57
- level: 'error',
58
- code: 'JSON_TRAILING_COMMA',
59
- message: 'Trailing comma detected (not allowed in JSON)',
112
+ level: "error",
113
+ code: "JSON_TRAILING_COMMA",
114
+ message: "Trailing comma detected (not allowed in JSON)",
60
115
  file: filePath,
61
116
  line: trailingCommaLine,
62
- suggestion: 'Remove the comma before the closing bracket/brace',
117
+ suggestion: "Remove the comma before the closing bracket/brace",
63
118
  });
64
119
  } else {
65
- // Generic JSON error
66
- let message = error.message;
67
- if (line && column) {
68
- message = `Invalid JSON at line ${line}, column ${column}: ${error.message}`;
69
- }
70
-
71
120
  results.push({
72
- level: 'error',
73
- code: 'JSON_SYNTAX',
74
- message,
121
+ level: "error",
122
+ code: "JSON_SYNTAX",
123
+ message: error.message,
75
124
  file: filePath,
76
125
  line,
77
126
  });
78
127
  }
128
+
129
+ return { valid: false, results, errorContext };
79
130
  }
80
131
 
81
132
  return { valid: false, results };