Added proper error handling for config file parsing errors

This commit is contained in:
Joaquin Coromina
2025-05-17 18:21:55 -04:00
parent 079ce49d17
commit 59dd7d7eea
3 changed files with 74 additions and 33 deletions
@@ -1,5 +1,5 @@
import { VoidSwitch } from '../util/inputs.js';
import { MCPServerEventParam, MCPServerObject, MCPServers } from '../../../../common/mcpServiceTypes.js';
import { MCPConfigParseError, MCPServerEventParam, MCPServerObject, MCPServers } from '../../../../common/mcpServiceTypes.js';
import { useEffect, useState } from 'react';
import { useAccessor } from '../util/services.js';
import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
@@ -102,6 +102,7 @@ const MCPServersList = () => {
const accessor = useAccessor();
const mcpService = accessor.get('IMCPService');
const [mcpServers, setMCPServers] = useState<MCPServers>({});
const [mcpConfigError, setMCPConfigError] = useState<string | null>(null);
// Get all servers from MCPConfigService
useEffect(() => {
@@ -114,35 +115,13 @@ const MCPServersList = () => {
setMCPServers(servers);
}
const handleListeners = (e: MCPServerEventParam) => {
if (e.response.event === 'add' || e.response.event === 'update' || e.response.event === 'loading') {
// Handle the add event
const { name, newServer } = e.response;
setMCPServers(prevServers => ({
...prevServers,
[name]: newServer
}));
return;
}
if (e.response.event === 'delete') {
// Handle the delete event
const { name, prevServer } = e.response;
setMCPServers(prevServers => {
const newServers = { ...prevServers };
delete newServers[name];
return newServers;
});
return;
}
throw new Error('Event not handled');
}
// Set up listeners for server events
const disposables: IDisposable[] = []
disposables.push(mcpService.onDidAddServer(handleListeners));
disposables.push(mcpService.onDidDeleteServer(handleListeners));
disposables.push(mcpService.onDidUpdateServer(handleListeners));
disposables.push(mcpService.onLoadingServers(handleListeners));
disposables.push(mcpService.onConfigParsingError(handleListeners));
// Clean up subscription when component unmounts
return () => {
@@ -152,14 +131,48 @@ const MCPServersList = () => {
}, [mcpService]);
const handleListeners = (e: MCPServerEventParam | MCPConfigParseError) => {
if (e.response.event === 'config-error') {
// Handle the config error event
const { error } = e.response;
setMCPConfigError(error);
return;
}
if (e.response.event === 'add' || e.response.event === 'update' || e.response.event === 'loading') {
// Handle the add event
const { name, newServer } = e.response;
setMCPServers(prevServers => ({
...prevServers,
[name]: newServer
}));
return;
}
if (e.response.event === 'delete') {
// Handle the delete event
const { name, prevServer } = e.response;
setMCPServers(prevServers => {
const newServers = { ...prevServers };
delete newServers[name];
return newServers;
});
return;
}
throw new Error('Event not handled');
}
return (
<div className="text-white rounded-md py-4">
<div>
{Object.entries(mcpServers).map(([name, server]) => (
{!mcpConfigError && Object.entries(mcpServers).map(([name, server]) => (
<div className="py-2" key={name}>
<MCPServer name={name} server={server} />
</div>
))}
{mcpConfigError && (
<div className="text-red-500 text-sm font-medium">
{mcpConfigError}
</div>
)}
</div>
</div>
);
@@ -15,7 +15,7 @@ import { IProductService } from '../../../../platform/product/common/productServ
import { VSBuffer } from '../../../../base/common/buffer.js';
import { IChannel } from '../../../../base/parts/ipc/common/ipc.js';
import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js';
import { MCPServers, MCPConfig, MCPServerEventParam, MCPServerEventAddParam, MCPServerEventUpdateParam, MCPServerEventDeleteParam, MCPServerEventLoadingParam } from './mcpServiceTypes.js';
import { MCPServers, MCPConfig, MCPServerEventParam, MCPServerEventAddParam, MCPServerEventUpdateParam, MCPServerEventDeleteParam, MCPServerEventLoadingParam, MCPConfigParseError } from './mcpServiceTypes.js';
import { Event, Emitter } from '../../../../base/common/event.js';
import { InternalToolInfo } from './prompt/prompts.js';
@@ -28,6 +28,7 @@ export interface IMCPService {
onDidUpdateServer: Event<MCPServerEventUpdateParam>;
onDidDeleteServer: Event<MCPServerEventDeleteParam>;
onLoadingServers: Event<MCPServerEventLoadingParam>;
onConfigParsingError: Event<MCPConfigParseError>;
}
export const IMCPService = createDecorator<IMCPService>('mcpConfigService');
@@ -51,10 +52,12 @@ class MCPService extends Disposable implements IMCPService {
private readonly _onDidUpdateServer = new Emitter<MCPServerEventUpdateParam>();
private readonly _onDidDeleteServer = new Emitter<MCPServerEventDeleteParam>();
private readonly _onLoadingServers = new Emitter<MCPServerEventLoadingParam>();
private readonly _onConfigParsingError = new Emitter<MCPConfigParseError>();
public readonly onDidAddServer = this._onDidAddServer.event;
public readonly onDidUpdateServer = this._onDidUpdateServer.event;
public readonly onDidDeleteServer = this._onDidDeleteServer.event;
public readonly onLoadingServers = this._onLoadingServers.event;
public readonly onConfigParsingError = this._onConfigParsingError.event;
constructor(
@IFileService private readonly fileService: IFileService,
@@ -110,12 +113,9 @@ class MCPService extends Disposable implements IMCPService {
// Parse the MCP config file
const mcpConfig = await this._parseMCPConfigFile();
if (mcpConfig) {
// Create the initial server list
// await this._createInitialServerList(mcpConfig);
if (mcpConfig && mcpConfig.mcpServers) {
// Setup the server list
console.log('MCP Config file parsed:', JSON.stringify(mcpConfig, null, 2));
this.channel.call('setupServers', mcpConfig)
}
@@ -170,14 +170,35 @@ class MCPService extends Disposable implements IMCPService {
}
private async _parseMCPConfigFile(): Promise<MCPConfig | null> {
// Remove any previous config parsing error
// This isn't super intuitive, but it works
this._onConfigParsingError.fire({
response: {
event: 'config-error',
error: null
}
});
// Process config file
const mcpConfigUri = await this._getMCPConfigPath();
try {
const fileContent = await this.fileService.readFile(mcpConfigUri);
const contentString = fileContent.value.toString();
return JSON.parse(contentString);
const configJson = JSON.parse(contentString);
if (!configJson.mcpServers) {
throw new Error('Invalid MCP config file: missing mcpServers property');
}
return configJson as MCPConfig;
} catch (error) {
console.error('Error reading or parsing MCP config file:', error);
const fullError = `Error parsing MCP config file: ${error}`;
console.error(fullError);
this._onConfigParsingError.fire({
response: {
event: 'config-error',
error: fullError
}
});
return null;
}
}
@@ -194,7 +215,6 @@ class MCPService extends Disposable implements IMCPService {
if (e.contains(mcpConfigUri)) {
const mcpConfig = await this._parseMCPConfigFile();
if (mcpConfig && mcpConfig.mcpServers) {
// Call the setupServers method in the main process
this.channel.call('setupServers', mcpConfig)
}
@@ -134,6 +134,14 @@ export interface MCPConfig {
mcpServers: Record<string, MCPServerConfig>;
}
export interface MCPConfigParseError {
// Error message
response: {
event: 'config-error';
error: string | null;
}
}
// SERVER EVENT TYPES ------------------------------------------
export interface MCPServerObject {