Added toggle service to turn mcp servers on and off

This commit is contained in:
Joaquin Coromina
2025-05-17 19:23:14 -04:00
parent 7862aa619a
commit 323a6d56f6
4 changed files with 79 additions and 12 deletions
@@ -3,6 +3,7 @@ import { MCPConfigParseError, MCPServerEventParam, MCPServerObject, MCPServers }
import { useEffect, useState } from 'react';
import { useAccessor } from '../util/services.js';
import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
import { off } from 'process';
export interface Tool {
/** Unique tool identifier */
@@ -33,11 +34,29 @@ interface MCPServerProps {
// MCP Server component
const MCPServer = ({ name, server }: MCPServerProps) => {
const accessor = useAccessor();
const mcpService = accessor.get('IMCPService');
const handleChangeEvent = (e: boolean) => {
// Handle the change event
mcpService.toggleServer(name, e);
}
// Needs to be raw CSS because
// Tailwind won't compile the colors
// dynamically
const serverStatusColorStyles = {
success: { backgroundColor: '#10B981' }, // green-500 equivalent
error: { backgroundColor: '#EF4444' }, // red-500 equivalent
loading: { backgroundColor: '#F59E0B' }, // yellow-500 equivalent
offline: { backgroundColor: '#6B7280' } // gray-500 equivalent
};
return (
<div className="border-b border-gray-800 bg-gray-300/10 py-4 rounded-lg ">
<div className="flex items-center mx-4">
{/* Status indicator */}
<div className={`w-2 h-2 rounded-full mr-2 ${server.status === 'success' ? 'bg-green-500' : server.status === 'error' ? 'bg-red-500' : 'bg-yellow-500'}`}></div>
<div className={`w-2 h-2 rounded-full mr-2`} style={serverStatusColorStyles[server.status]}></div>
{/* Server name */}
<div className="text-sm font-medium mr-2">{name}</div>
@@ -47,9 +66,7 @@ const MCPServer = ({ name, server }: MCPServerProps) => {
<VoidSwitch
value={server.isOn}
disabled={server.status === 'error'}
onChange={() => {
server.isOn = !server.isOn;
}}
onChange={handleChangeEvent}
/>
</div>
</div>
@@ -57,7 +74,7 @@ const MCPServer = ({ name, server }: MCPServerProps) => {
{/* Tools section */}
<div className="mt-1 mx-4">
<div className="flex flex-wrap gap-2 max-h-32 overflow-y-auto pb-1">
{server.tools.length > 0 ? (
{server.isOn && server.tools.length > 0 ? (
server.tools.map((tool) => (
<span
key={tool.name}
@@ -74,7 +91,7 @@ const MCPServer = ({ name, server }: MCPServerProps) => {
</div>
{/* Command display */}
{server.command && (
{server.isOn && server.command && (
<div className="mt-2 mx-4">
<div className="text-xs text-gray-400">Command:</div>
<CommandDisplay command={server.command} />
@@ -24,6 +24,7 @@ export interface IMCPService {
openMCPConfigFile(): Promise<void>;
getMCPServers(): MCPServers;
getAllToolsFormatted(): InternalToolInfo[];
toggleServer(serverName: string, isOn: boolean): Promise<void>;
onDidAddServer: Event<MCPServerEventAddParam>;
onDidUpdateServer: Event<MCPServerEventUpdateParam>;
onDidDeleteServer: Event<MCPServerEventDeleteParam>;
@@ -215,7 +216,7 @@ 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
// Set up the server list
this.channel.call('setupServers', mcpConfig)
}
}
@@ -280,6 +281,10 @@ class MCPService extends Disposable implements IMCPService {
return allTools;
}
public async toggleServer(serverName: string, isOn: boolean): Promise<void> {
this.channel.call('toggleServer', { serverName, isOn })
}
// utility functions
private async _getMCPConfigPath(): Promise<URI> {
@@ -147,7 +147,7 @@ export interface MCPConfigParseError {
export interface MCPServerObject {
// Command-based server properties
tools: Tool[],
status: 'loading' | 'error' | 'success',
status: 'loading' | 'error' | 'success' | 'offline',
isOn: boolean,
command?: string,
error?: string,
@@ -77,7 +77,7 @@ export class MCPChannel implements IServerChannel {
await this._callCloseAllServers()
}
else if (command === 'toggleServer') {
// TODO: HANDLE THIS
await this._handleToggleServer(params.serverName, params.isOn)
}
else if (command === 'callTool') {
// TODO: HANDLE THIS
@@ -179,6 +179,7 @@ export class MCPChannel implements IServerChannel {
const deletePromises: Promise<MCPDeleteResponse>[] = deletedServers.map(async (serverName) => {
const prevServer = this.clients[serverName]?.formattedServer;
await this._callCloseServer(serverName)
this._callRemoveServer(serverName)
return {
event: 'delete',
prevServer,
@@ -292,6 +293,7 @@ export class MCPChannel implements IServerChannel {
private async _callCloseAllServers() {
for (const serverName in this.clients) {
await this._callCloseServer(serverName)
this._callRemoveServer(serverName)
}
console.log('Closed all MCP servers');
}
@@ -302,21 +304,64 @@ export class MCPChannel implements IServerChannel {
if (client) {
await client.close()
}
delete this.clients[serverName]
console.log(`Closed MCP server ${serverName}`);
}
}
private _callRemoveServer(serverName: string) {
if (this.clients[serverName]) {
delete this.clients[serverName]
console.log(`Removed MCP server ${serverName}`);
}
}
private async _handleToggleServer(serverName: string, isOn: boolean) {
const prevServer = this.clients[serverName]?.formattedServer
if (isOn) {
// Handle turning on the server
this.mcpEmitters.serverEvent.loading.fire(this._getLoadingServerObject(serverName, isOn))
const formattedServer = await this._callSetupServer(this.clients[serverName].mcpConfig, serverName)
this.mcpEmitters.serverEvent.update.fire({
response: {
event: 'update',
name: serverName,
newServer: formattedServer,
prevServer: prevServer,
}
})
} else {
// Handle turning off the server
this.mcpEmitters.serverEvent.loading.fire(this._getLoadingServerObject(serverName, isOn))
this._callCloseServer(serverName)
this.mcpEmitters.serverEvent.update.fire({
response: {
event: 'update',
name: serverName,
newServer: {
status: 'offline',
isOn,
tools: [],
command: '',
// Explicitly set error to undefined
// to reset the error state
error: undefined,
},
prevServer: prevServer,
}
})
}
}
// Util functions
private _getLoadingServerObject(serverName: string): MCPServerEventLoadingParam {
private _getLoadingServerObject(serverName: string, isOn = true): MCPServerEventLoadingParam {
return {
response: {
event: 'loading',
name: serverName,
newServer: {
status: 'loading',
isOn: false,
isOn,
tools: [],
command: '',
}