From 57bc32ac9b4c0b6c2a072bba46a95403cfab35c7 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Wed, 21 May 2025 22:51:55 -0700 Subject: [PATCH] update loading? --- .../void/browser/react/src/util/services.tsx | 2 +- .../react/src/void-settings-tsx/Settings.tsx | 14 ++-- .../contrib/void/common/mcpService.ts | 82 +++++++------------ .../contrib/void/common/mcpServiceTypes.ts | 1 - .../void/common/voidSettingsService.ts | 44 +++++----- .../contrib/void/common/voidSettingsTypes.ts | 6 +- .../contrib/void/electron-main/mcpChannel.ts | 15 ++-- 7 files changed, 67 insertions(+), 97 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx index de5ff82a..dc67784c 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------*/ import React, { useState, useEffect, useCallback } from 'react' -import { MCPServerState, RefreshableProviderName, SettingsOfProvider } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js' +import { MCPUserState, RefreshableProviderName, SettingsOfProvider } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js' import { IDisposable } from '../../../../../../../base/common/lifecycle.js' import { VoidSettingsState } from '../../../../../../../workbench/contrib/void/common/voidSettingsService.js' import { ColorScheme } from '../../../../../../../platform/theme/common/theme.js' diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index f901ad7b..76d2c17e 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -910,6 +910,9 @@ const MCPServer = ({ name, server }: { name: string, server: MCPServerObject }) mcpService.toggleMCPServer(name, e); } + const voidSettings = useSettingsState() + const isOn = voidSettings.mcpUserStateOfName[name]?.isOn + return (
@@ -929,7 +932,7 @@ const MCPServer = ({ name, server }: { name: string, server: MCPServerObject }) {/* Power toggle switch */}
@@ -939,7 +942,7 @@ const MCPServer = ({ name, server }: { name: string, server: MCPServerObject }) {/* Tools section */}
- {server.isOn && server.tools.length > 0 ? ( + {isOn && server.tools.length > 0 ? ( server.tools.map((tool: { name: string; description?: string }) => ( {/* Command badge */} - {server.isOn && server.command && ( + {isOn && server.command && (
Command:
@@ -983,11 +986,6 @@ const MCPServer = ({ name, server }: { name: string, server: MCPServerObject }) // Main component that renders the list of servers const MCPServersList = () => { const mcpServiceState = useMCPServiceState() - const accessor = useAccessor(); - - const userSpecifiedMCPServerNames = mcpServiceState.userSpecifiedMCPServerNames - // TODO tell the user what servers they've specified (might be different from those found) - let content: React.ReactNode if (mcpServiceState.error) { diff --git a/src/vs/workbench/contrib/void/common/mcpService.ts b/src/vs/workbench/contrib/void/common/mcpService.ts index 3b73af87..8868b9fb 100644 --- a/src/vs/workbench/contrib/void/common/mcpService.ts +++ b/src/vs/workbench/contrib/void/common/mcpService.ts @@ -14,17 +14,16 @@ 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 { MCPServerOfName, MCPConfigFileType, MCPAddServerResponse, MCPUpdateServerResponse, MCPDeleteServerResponse, MCPServerEventResponse, MCPServerObject, MCPToolCallParams, MCPGenericToolResponse } from './mcpServiceTypes.js'; +import { MCPServerOfName, MCPConfigFileType, MCPAddServerResponse, MCPUpdateServerResponse, MCPDeleteServerResponse, MCPServerObject, MCPToolCallParams, MCPGenericToolResponse } from './mcpServiceTypes.js'; import { Event, Emitter } from '../../../../base/common/event.js'; import { InternalToolInfo } from './prompt/prompts.js'; import { IVoidSettingsService } from './voidSettingsService.js'; -import { MCPServerStateOfName } from './voidSettingsTypes.js'; +import { MCPUserStateOfName } from './voidSettingsTypes.js'; type MCPState = { mcpServerOfName: MCPServerOfName, - error: string | undefined, - userSpecifiedMCPServerNames: string[], + error: string | undefined, // global parsing error } export interface IMCPService { @@ -77,7 +76,6 @@ class MCPService extends Disposable implements IMCPService { state: MCPState = { mcpServerOfName: {}, error: undefined, - userSpecifiedMCPServerNames: [], } // Emitters for server events @@ -96,15 +94,12 @@ class MCPService extends Disposable implements IMCPService { @IVoidSettingsService private readonly voidSettingsService: IVoidSettingsService, ) { super(); - // Register the service with the instantiation service this.channel = this.mainProcessService.getChannel('void-channel-mcp') - // Register listeners for the channel - this._register((this.channel.listen('onAdd_server') satisfies Event)(e => this._onGetServerEvent(e))); - this._register((this.channel.listen('onUpdate_server') satisfies Event)(e => this._onGetServerEvent(e))); - this._register((this.channel.listen('onDelete_server') satisfies Event)(e => this._onGetServerEvent(e))); - // this._register((this.channel.listen('onLoading_server') satisfies Event)(e => this._onServerEvent(e))); - // Initialize the service + this._register((this.channel.listen('onAdd_server') satisfies Event)(e => { this._setMCPServerState(e.response.name, e.response.newServer) })); + this._register((this.channel.listen('onUpdate_server') satisfies Event)(e => { this._setMCPServerState(e.response.name, e.response.newServer) })); + this._register((this.channel.listen('onDelete_server') satisfies Event)(e => { this._setMCPServerState(e.response.name, e.response.newServer) })); + this._initialize(); } @@ -127,12 +122,7 @@ class MCPService extends Disposable implements IMCPService { } } - private async _onGetServerEvent(e: MCPServerEventResponse) { - this._setMCPServer(e.response.name, e.response.newServer) - } - - - private readonly _setMCPServer = async (serverName: string, newServer: MCPServerObject | undefined) => { + private readonly _setMCPServerState = async (serverName: string, newServer: MCPServerObject | undefined) => { this.state = { ...this.state, mcpServerOfName: { @@ -261,49 +251,37 @@ class MCPService extends Disposable implements IMCPService { private async _refreshMCPServers(): Promise { this._setHasError(undefined) - // TODO!!! set is loading - const mcpConfigFile = await this._parseMCPConfigFile(); - if (!mcpConfigFile) { console.log(`Not setting state: MCP config file not found`); return } - if (!mcpConfigFile?.mcpServers) { console.log(`Not setting state: MCP config file did not have an 'mcpServers' field`); return } + const newConfigFileJSON = await this._parseMCPConfigFile(); + if (!newConfigFileJSON) { console.log(`Not setting state: MCP config file not found`); return } + if (!newConfigFileJSON?.mcpServers) { console.log(`Not setting state: MCP config file did not have an 'mcpServers' field`); return } - // set state to loading if it's the first time we're seeing it - const mcpConfigOfName = mcpConfigFile.mcpServers + + const oldConfigFileNames = Object.keys(this.state.mcpServerOfName) + const newConfigFileNames = Object.keys(newConfigFileJSON.mcpServers) + + const addedServerNames = newConfigFileNames.filter(serverName => !oldConfigFileNames.includes(serverName)); // in new and not in old + const removedServerNames = oldConfigFileNames.filter(serverName => !newConfigFileNames.includes(serverName)); // in old and not in new + + // set isOn to any new servers in the config + const addedUserStateOfName: MCPUserStateOfName = {} + for (const name in addedServerNames) { addedUserStateOfName[name] = { isOn: true } } + await this.voidSettingsService.addMCPUserStateOfNames(addedUserStateOfName); + + // delete isOn for any servers that no longer show up in the config + await this.voidSettingsService.removeMCPUserStateOfNames(removedServerNames); + + // set all servers to loading + const mcpConfigOfName = newConfigFileJSON.mcpServers for (const serverName in mcpConfigOfName) { if (serverName in this.state.mcpServerOfName) continue - this._setMCPServer(serverName, { - isOn: false, + this._setMCPServerState(serverName, { status: 'loading', - error: undefined, - command: undefined, tools: [], }) } - const currMCPStateOfName = this.voidSettingsService.state.mcpServerStateOfName; - const availableServers = Object.keys(mcpConfigFile.mcpServers); - - // Handle added servers - const addedServers = availableServers.filter(serverName => !currMCPStateOfName[serverName]?.isOn); - const addedServersObject = addedServers.reduce((acc, serverName) => { - acc[serverName] = { isOn: true }; - return acc; - }, {} as MCPServerStateOfName); - await this.voidSettingsService.addMCPServerStateOfName(addedServersObject); - - // Handle removed servers - const removedServers = Object.keys(currMCPStateOfName).filter(serverName => availableServers.indexOf(serverName) === -1); - await this.voidSettingsService.removeMCPServerStateNames(removedServers); - - // Compile the updated server list as MCPServerStates - const updatedServers = Object.keys(currMCPStateOfName).reduce((acc, serverName) => { - if (availableServers.includes(serverName)) { - acc[serverName] = currMCPStateOfName[serverName]; - } - return acc; - }, {} as MCPServerStateOfName); - - this.channel.call('refreshMCPServers', { mcpConfig: mcpConfigFile, serverStates: updatedServers }) + this.channel.call('refreshMCPServers', { mcpConfig: newConfigFileJSON, userStateOfName: this.voidSettingsService.state.mcpUserStateOfName }) } public async callMCPTool(toolData: MCPToolCallParams): Promise { diff --git a/src/vs/workbench/contrib/void/common/mcpServiceTypes.ts b/src/vs/workbench/contrib/void/common/mcpServiceTypes.ts index cc568191..61bcf095 100644 --- a/src/vs/workbench/contrib/void/common/mcpServiceTypes.ts +++ b/src/vs/workbench/contrib/void/common/mcpServiceTypes.ts @@ -138,7 +138,6 @@ export interface MCPServerObject { // Command-based server properties tools: MCPTool[], status: 'loading' | 'error' | 'success' | 'offline', - isOn: boolean | undefined, command?: string, error?: string, } diff --git a/src/vs/workbench/contrib/void/common/voidSettingsService.ts b/src/vs/workbench/contrib/void/common/voidSettingsService.ts index 2458b1cd..b8f0b2dd 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsService.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsService.ts @@ -13,7 +13,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo import { IMetricsService } from './metricsService.js'; import { defaultProviderSettings, getModelCapabilities, ModelOverrides } from './modelCapabilities.js'; import { VOID_SETTINGS_STORAGE_KEY } from './storageKeys.js'; -import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, VoidStatefulModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, ModelSelectionOptions, OptionsOfModelSelection, ChatMode, OverridesOfModel, defaultOverridesOfModel, MCPServerStateOfName, MCPServerState } from './voidSettingsTypes.js'; +import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, VoidStatefulModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, ModelSelectionOptions, OptionsOfModelSelection, ChatMode, OverridesOfModel, defaultOverridesOfModel, MCPUserStateOfName as MCPUserStateOfName, MCPUserState } from './voidSettingsTypes.js'; // name is the name in the dropdown @@ -43,7 +43,7 @@ export type VoidSettingsState = { readonly optionsOfModelSelection: OptionsOfModelSelection; readonly overridesOfModel: OverridesOfModel; readonly globalSettings: GlobalSettings; - readonly mcpServerStateOfName: MCPServerStateOfName; + readonly mcpUserStateOfName: MCPUserStateOfName; // user-controlled state of MCP servers readonly _modelOptions: ModelOption[] // computed based on the two above items } @@ -76,9 +76,9 @@ export interface IVoidSettingsService { addModel(providerName: ProviderName, modelName: string): void; deleteModel(providerName: ProviderName, modelName: string): boolean; - addMCPServerStateOfName(serverStateOfName: MCPServerStateOfName): Promise; - removeMCPServerStateNames(serverNames: string[]): Promise; - setMCPServerState(serverName: string, state: MCPServerState): Promise; + addMCPUserStateOfNames(userStateOfName: MCPUserStateOfName): Promise; + removeMCPUserStateOfNames(serverNames: string[]): Promise; + setMCPServerState(serverName: string, state: MCPUserState): Promise; } @@ -218,7 +218,7 @@ const defaultState = () => { optionsOfModelSelection: { 'Chat': {}, 'Ctrl+K': {}, 'Autocomplete': {}, 'Apply': {} }, overridesOfModel: deepClone(defaultOverridesOfModel), _modelOptions: [], // computed later - mcpServerStateOfName: {}, + mcpUserStateOfName: {}, } return d } @@ -368,7 +368,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { const newGlobalSettings = this.state.globalSettings const newOverridesOfModel = this.state.overridesOfModel - const newMCPServerStateOfName = this.state.mcpServerStateOfName + const newMCPUserStateOfName = this.state.mcpUserStateOfName const newState = { modelSelectionOfFeature: newModelSelectionOfFeature, @@ -376,7 +376,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { settingsOfProvider: newSettingsOfProvider, globalSettings: newGlobalSettings, overridesOfModel: newOverridesOfModel, - mcpServerStateOfName: newMCPServerStateOfName, + mcpUserStateOfName: newMCPUserStateOfName, } this.state = _validatedModelState(newState) @@ -541,11 +541,11 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { } // MCP Server State - private _setMCPServerStateOfName = async (newStates: MCPServerStateOfName) => { + private _setMCPUserStateOfName = async (newStates: MCPUserStateOfName) => { const newState: VoidSettingsState = { ...this.state, - mcpServerStateOfName: { - ...this.state.mcpServerStateOfName, + mcpUserStateOfName: { + ...this.state.mcpUserStateOfName, ...newStates } }; @@ -555,18 +555,18 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { this._metricsService.capture('Set MCP Server States', { newStates }); } - addMCPServerStateOfName = async (newMCPStates: MCPServerStateOfName) => { - const { mcpServerStateOfName: mcpServerStates } = this.state + addMCPUserStateOfNames = async (newMCPStates: MCPUserStateOfName) => { + const { mcpUserStateOfName: mcpServerStates } = this.state const newMCPServerStates = { ...mcpServerStates, ...newMCPStates, } - await this._setMCPServerStateOfName(newMCPServerStates) - this._metricsService.capture('Add MCP Server', { servers: Object.keys(newMCPStates).join(', ') }); + await this._setMCPUserStateOfName(newMCPServerStates) + this._metricsService.capture('Add MCP Servers', { servers: Object.keys(newMCPStates).join(', ') }); } - removeMCPServerStateNames = async (serverNames: string[]) => { - const { mcpServerStateOfName: mcpServerStates } = this.state + removeMCPUserStateOfNames = async (serverNames: string[]) => { + const { mcpUserStateOfName: mcpServerStates } = this.state const newMCPServerStates = { ...mcpServerStates, } @@ -575,18 +575,18 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService { delete newMCPServerStates[serverName] } }) - await this._setMCPServerStateOfName(newMCPServerStates) - this._metricsService.capture('Remove MCP Server', { servers: serverNames.join(', ') }); + await this._setMCPUserStateOfName(newMCPServerStates) + this._metricsService.capture('Remove MCP Servers', { servers: serverNames.join(', ') }); } - setMCPServerState = async (serverName: string, state: MCPServerState) => { - const { mcpServerStateOfName: mcpServerStates } = this.state + setMCPServerState = async (serverName: string, state: MCPUserState) => { + const { mcpUserStateOfName: mcpServerStates } = this.state if (!(serverName in mcpServerStates)) return // if not in list, do nothing const newMCPServerStates = { ...mcpServerStates, [serverName]: state, } - await this._setMCPServerStateOfName(newMCPServerStates) + await this._setMCPUserStateOfName(newMCPServerStates) this._metricsService.capture('Update MCP Server State', { serverName, state }); } diff --git a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts index e2faf506..bdb34504 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts @@ -494,10 +494,10 @@ export const defaultOverridesOfModel = overridesOfModel -export interface MCPServerStateOfName { - [serverName: string]: MCPServerState | undefined; +export interface MCPUserStateOfName { + [serverName: string]: MCPUserState | undefined; } -export interface MCPServerState { +export interface MCPUserState { isOn: boolean; } diff --git a/src/vs/workbench/contrib/void/electron-main/mcpChannel.ts b/src/vs/workbench/contrib/void/electron-main/mcpChannel.ts index 977ea841..0dcf98fa 100644 --- a/src/vs/workbench/contrib/void/electron-main/mcpChannel.ts +++ b/src/vs/workbench/contrib/void/electron-main/mcpChannel.ts @@ -17,7 +17,7 @@ import { MCPConfigFileType, MCPConfigFileServerType, MCPServerErrorModel, MCPSer import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'; import { CallToolResult } from '@modelcontextprotocol/sdk/types.js'; import { equals } from '../../../../base/common/objects.js'; -import { MCPServerStateOfName } from '../common/voidSettingsTypes.js'; +import { MCPUserStateOfName } from '../common/voidSettingsTypes.js'; // const getLoadingServerObject = (serverName: string, isOn: boolean | undefined) => { @@ -118,9 +118,9 @@ export class MCPChannel implements IServerChannel { // server functions - private async _refreshMCPServers(params: { mcpConfig: MCPConfigFileType, serverStates: MCPServerStateOfName }) { + private async _refreshMCPServers(params: { mcpConfig: MCPConfigFileType, userStateOfName: MCPUserStateOfName }) { - const { mcpConfig, serverStates } = params + const { mcpConfig, userStateOfName } = params // Get all prevServers const prevServers = { ...this.clients } @@ -174,7 +174,7 @@ export class MCPChannel implements IServerChannel { if (addedServers.length > 0) { // emit added servers const addPromises = addedServers.map(async (serverName) => { - const addedServer = await this._safeSetupServer(mcpServers[serverName], serverName, serverStates[serverName]?.isOn) + const addedServer = await this._safeSetupServer(mcpServers[serverName], serverName, userStateOfName[serverName]?.isOn) return { type: 'add', newServer: addedServer, @@ -189,7 +189,7 @@ export class MCPChannel implements IServerChannel { // emit updated servers const updatePromises = updatedServers.map(async (serverName) => { const prevServer = this.clients[serverName]?.formattedServer; - const newServer = await this._safeSetupServer(mcpServers[serverName], serverName, serverStates[serverName]?.isOn) + const newServer = await this._safeSetupServer(mcpServers[serverName], serverName, userStateOfName[serverName]?.isOn) return { type: 'update', prevServer, @@ -234,7 +234,6 @@ export class MCPChannel implements IServerChannel { const { tools } = await client.listTools() formattedServer = { status: isOn ? 'success' : 'offline', - isOn, tools: tools, command: server.url.toString(), } @@ -245,7 +244,6 @@ export class MCPChannel implements IServerChannel { console.log(`Connected via SSE to ${serverName}`); formattedServer = { status: isOn ? 'success' : 'offline', - isOn, tools: [], command: server.url.toString(), } @@ -272,7 +270,6 @@ export class MCPChannel implements IServerChannel { // Format server object formattedServer = { status: isOn ? 'success' : 'offline', - isOn, tools: tools, command: fullCommand, } @@ -301,7 +298,6 @@ export class MCPChannel implements IServerChannel { const formattedError: MCPServerErrorModel = { status: 'error', - isOn: false, tools: [], error: typedErr.message, command: fullCommand, @@ -369,7 +365,6 @@ export class MCPChannel implements IServerChannel { name: serverName, newServer: { status: 'offline', - isOn, tools: [], command: '', // Explicitly set error to undefined