Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions __tests__/__fixtures__/intellij-empty-with-comment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"servers": {
// add your MCP servers configuration here
}
}
1 change: 1 addition & 0 deletions __tests__/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ describe('CLI program', () => {
const mockedMCPFileFixtures = [
'__tests__/__fixtures__/mcp.json',
'__tests__/__fixtures__/subdir/mcp2.json',
'__tests__/__fixtures__/intellij-empty-with-comment.json',
]

const mcpFilesManager = new MCPFiles(mockedMCPFileFixtures)
Expand Down
13 changes: 11 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@
"type": "git",
"url": "https://github.com/lirantal/ls-mcp.git"
},
"dependencies": {
"jsonc-parser": "^3.3.1"
},
"devDependencies": {
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.7",
Expand Down
57 changes: 42 additions & 15 deletions src/services/mcp-config-linter-service.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,50 @@
import fs from 'node:fs/promises'
import { parse } from 'jsonc-parser'

export class MCPConfigLinterService {
private filePath: string
private fileContents: string | null = null
private parsed: boolean = false
private valid: boolean = false
private fileContentsData: any | null = null

constructor (filePath: string) {
this.filePath = filePath
}

async isValidSyntax (): Promise<boolean> {
async parseFile (): Promise<void> {
if (this.parsed) {
return
}
this.parsed = true

const fileContent = await this.getFileContent()

// @TODO this should also support YAML files and other formats
try {
const fileContent = await this.getFileContent()
JSON.parse(fileContent)
return true
this.fileContentsData = JSON.parse(fileContent)
if (typeof this.fileContentsData === 'object') {
this.valid = true
return
}
} catch (e) {
// ignore
}

try {
this.fileContentsData = parse(fileContent)
if (typeof this.fileContentsData === 'object') {
this.valid = true
}
} catch (error) {
// ignore
}
}

async isValidSyntax (): Promise<boolean> {
try {
await this.parseFile()
return this.valid
} catch (error) {
return false
}
Expand All @@ -25,25 +56,21 @@ export class MCPConfigLinterService {
}

async getMCPServers (): Promise<Record<string, object>> {
const fileContentRaw = await this.getFileContent()

// @TODO parse this file that may have comments inside it
// can use comment-json npm package or similar
const fileContentsData = JSON.parse(fileContentRaw)
await this.parseFile()

// VS Code uses `servers`
if (fileContentsData?.servers) {
return fileContentsData.servers
if (this.fileContentsData?.servers) {
return this.fileContentsData.servers
}

// VS Code global settings.json file uses `mcp` -> `servers`
if (fileContentsData?.mcp?.servers) {
return fileContentsData.mcp.servers
if (this.fileContentsData?.mcp?.servers) {
return this.fileContentsData.mcp.servers
}

// Claude and Cursor use the `mcpServers` key
if (fileContentsData?.mcpServers) {
return fileContentsData.mcpServers
if (this.fileContentsData?.mcpServers) {
return this.fileContentsData.mcpServers
}

return {}
Expand Down
Loading