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 76d2c17e..d8b81fb4 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
@@ -19,7 +19,7 @@ import { ToolApprovalType, toolApprovalTypes } from '../../../../common/toolsSer
import Severity from '../../../../../../../base/common/severity.js'
import { getModelCapabilities, modelOverrideKeys, ModelOverrides } from '../../../../common/modelCapabilities.js';
import { TransferEditorType, TransferFilesInfo } from '../../../extensionTransferTypes.js';
-import { MCPServerObject } from '../../../../common/mcpServiceTypes.js';
+import { MCPServer } from '../../../../common/mcpServiceTypes.js';
import { useMCPServiceState } from '../util/services.js';
const ButtonLeftTextRightOption = ({ text, leftButton }: { text: string, leftButton?: React.ReactNode }) => {
@@ -901,7 +901,7 @@ export const OneClickSwitchButton = ({ fromEditor = 'VS Code', className = '' }:
// full settings
// MCP Server component
-const MCPServer = ({ name, server }: { name: string, server: MCPServerObject }) => {
+const MCPServer = ({ name, server }: { name: string, server: MCPServer }) => {
const accessor = useAccessor();
const mcpService = accessor.get('IMCPService');
@@ -942,8 +942,8 @@ const MCPServer = ({ name, server }: { name: string, server: MCPServerObject })
{/* Tools section */}
- {isOn && server.tools.length > 0 ? (
- server.tools.map((tool: { name: string; description?: string }) => (
+ {isOn && (server.tools ?? []).length > 0 ? (
+ (server.tools ?? []).map((tool: { name: string; description?: string }) => (
;
toggleMCPServer(serverName: string, isOn: boolean): Promise;
- readonly state: MCPState; // NOT persisted
+ readonly state: MCPServiceState; // NOT persisted
onDidChangeState: Event;
getCurrentMCPToolNames(): InternalToolInfo[];
@@ -73,7 +73,7 @@ class MCPService extends Disposable implements IMCPService {
private readonly channel: IChannel // MCPChannel
// list of MCP servers pulled from mcpChannel
- state: MCPState = {
+ state: MCPServiceState = {
mcpServerOfName: {},
error: undefined,
}
@@ -122,7 +122,7 @@ class MCPService extends Disposable implements IMCPService {
}
}
- private readonly _setMCPServerState = async (serverName: string, newServer: MCPServerObject | undefined) => {
+ private readonly _setMCPServerState = async (serverName: string, newServer: MCPServer | undefined) => {
this.state = {
...this.state,
mcpServerOfName: {
@@ -180,7 +180,7 @@ class MCPService extends Disposable implements IMCPService {
public getCurrentMCPToolNames(): InternalToolInfo[] {
const allTools = Object.entries(this.state.mcpServerOfName).flatMap(([serverName, server]) => {
- return server.tools.map(tool => {
+ return server.tools?.map(tool => {
// Convert JsonSchema to the expected format
const convertedParams: { [paramName: string]: { description: string } } = {};
@@ -200,7 +200,7 @@ class MCPService extends Disposable implements IMCPService {
serverName,
};
});
- });
+ }).filter(s => s !== undefined)
return allTools;
}
@@ -229,7 +229,7 @@ class MCPService extends Disposable implements IMCPService {
}
- private async _parseMCPConfigFile(): Promise {
+ private async _parseMCPConfigFile(): Promise {
const mcpConfigUri = await this._getMCPConfigFilePath();
try {
const fileContent = await this.fileService.readFile(mcpConfigUri);
@@ -238,7 +238,7 @@ class MCPService extends Disposable implements IMCPService {
if (!configFileJson.mcpServers) {
throw new Error('Missing mcpServers property');
}
- return configFileJson as MCPConfigFileType;
+ return configFileJson as MCPConfigFileJSON;
} catch (error) {
const fullError = `Error parsing MCP config file: ${error}`;
this._setHasError(fullError)
@@ -281,7 +281,7 @@ class MCPService extends Disposable implements IMCPService {
})
}
- this.channel.call('refreshMCPServers', { mcpConfig: newConfigFileJSON, userStateOfName: this.voidSettingsService.state.mcpUserStateOfName })
+ this.channel.call('refreshMCPServers', { mcpConfigFileJSON: 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 61bcf095..eb572bc7 100644
--- a/src/vs/workbench/contrib/void/common/mcpServiceTypes.ts
+++ b/src/vs/workbench/contrib/void/common/mcpServiceTypes.ts
@@ -116,7 +116,7 @@ export interface MCPTool {
// MCP SERVER CONFIG FILE TYPES -----------------------------
-export interface MCPConfigFileServerType {
+export interface MCPConfigFileEntryJSON {
// Command-based server properties
command?: string;
args?: string[];
@@ -127,73 +127,60 @@ export interface MCPConfigFileServerType {
headers?: Record;
}
-export interface MCPConfigFileType {
- mcpServers: Record;
+export interface MCPConfigFileJSON {
+ mcpServers: Record;
}
// SERVER EVENT TYPES ------------------------------------------
-export interface MCPServerObject {
+export type MCPServer = {
// Command-based server properties
tools: MCPTool[],
- status: 'loading' | 'error' | 'success' | 'offline',
+ status: 'loading' | 'success' | 'offline',
command?: string,
error?: string,
+} | {
+ tools?: undefined,
+ status: 'error',
+ command?: string,
+ error: string,
}
export interface MCPServerOfName {
- [serverName: string]: MCPServerObject;
+ [serverName: string]: MCPServer;
}
-// Create separate types for success and error cases
-export type MCPServerSuccessModel = MCPServerObject;
-export type MCPServerErrorModel = Omit & { error: string };
-
-
-export type MCPServerSetupParams = {
- serverName: string;
- onSuccess: (param: { model: MCPServerSuccessModel & { serverName: string } }) => void;
- onError: (param: { model: MCPServerErrorModel & { serverName: string } }) => void;
-}
-
-// Listener event types
-export type EventMCPServerSetupOnSuccess = Parameters[0]
-export type EventMCPServerSetupOnError = Parameters[0]
-
-export type MCPServerModel = MCPServerSuccessModel | MCPServerErrorModel;
-
-
-export type MCPServerEventType = {
+export type MCPServerEvent = {
type: 'add';
name: string;
prevServer?: undefined;
- newServer: MCPServerModel;
+ newServer: MCPServer;
} | {
type: 'update';
name: string;
- prevServer: MCPServerModel;
- newServer: MCPServerModel;
+ prevServer: MCPServer;
+ newServer: MCPServer;
} | {
type: 'delete';
name: string;
newServer?: undefined;
- prevServer: MCPServerModel;
+ prevServer: MCPServer;
} | {
type: 'loading';
name: string;
prevServer?: undefined;
- newServer: MCPServerModel;
+ newServer: MCPServer;
}
// Response types
-export type MCPAddServerResponse = { response: MCPServerEventType & { type: 'add' } }
-export type MCPUpdateServerResponse = { response: MCPServerEventType & { type: 'update' } }
-export type MCPDeleteServerResponse = { response: MCPServerEventType & { type: 'delete' } }
-// export type MCPLoadingChangeResponse = { response: MCPEventType & { type: 'loading' } }
+export type MCPServerEventResponse = { response: MCPServerEvent }
+
+export type MCPAddServerResponse = { response: MCPServerEvent & { type: 'add' } }
+export type MCPUpdateServerResponse = { response: MCPServerEvent & { type: 'update' } }
+export type MCPDeleteServerResponse = { response: MCPServerEvent & { type: 'delete' } }
-export type MCPServerEventResponse = { response: MCPServerEventType }
export interface MCPConfigFileParseErrorResponse {
response: {
diff --git a/src/vs/workbench/contrib/void/electron-main/mcpChannel.ts b/src/vs/workbench/contrib/void/electron-main/mcpChannel.ts
index 0dcf98fa..f9bd844b 100644
--- a/src/vs/workbench/contrib/void/electron-main/mcpChannel.ts
+++ b/src/vs/workbench/contrib/void/electron-main/mcpChannel.ts
@@ -13,28 +13,12 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
-import { MCPConfigFileType, MCPConfigFileServerType, MCPServerErrorModel, MCPServerModel, MCPAddServerResponse, MCPUpdateServerResponse, MCPDeleteServerResponse, MCPGenericToolResponse, MCPToolErrorResponse } from '../common/mcpServiceTypes.js';
+import { MCPConfigFileJSON, MCPConfigFileEntryJSON, MCPServer, MCPAddServerResponse, MCPUpdateServerResponse, MCPDeleteServerResponse, MCPGenericToolResponse, MCPToolErrorResponse } from '../common/mcpServiceTypes.js';
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { equals } from '../../../../base/common/objects.js';
import { MCPUserStateOfName } from '../common/voidSettingsTypes.js';
-
-// const getLoadingServerObject = (serverName: string, isOn: boolean | undefined) => {
-// return {
-// response: {
-// event: 'loading',
-// name: serverName,
-// newServer: {
-// status: 'loading',
-// isOn,
-// tools: [],
-// command: '',
-// }
-// }
-// } as const
-// }
-
const getClientConfig = (serverName: string) => {
return {
name: `${serverName}-client`,
@@ -44,10 +28,26 @@ const getClientConfig = (serverName: string) => {
}
+
+type MCPServerNonError = MCPServer & { status: Omit }
+type MCPServerError = MCPServer & { status: 'error' }
+
+
+
export class MCPChannel implements IServerChannel {
// connected clients
- private clients: { [clientId: string]: { client?: Client, mcpConfig: MCPConfigFileServerType, formattedServer: MCPServerModel } } = {}
+ private infoOfClientId: {
+ [clientId: string]: {
+ _client: Client,
+ mcpConfig: MCPConfigFileEntryJSON,
+ mcpServer: MCPServerNonError,
+ } | {
+ _client?: undefined,
+ mcpConfig: MCPConfigFileEntryJSON,
+ mcpServer: MCPServerError,
+ }
+ } = {}
// mcp emitters
private readonly mcpEmitters = {
@@ -118,21 +118,17 @@ export class MCPChannel implements IServerChannel {
// server functions
- private async _refreshMCPServers(params: { mcpConfig: MCPConfigFileType, userStateOfName: MCPUserStateOfName }) {
+ private async _refreshMCPServers(params: { mcpConfigFileJSON: MCPConfigFileJSON, userStateOfName: MCPUserStateOfName }) {
- const { mcpConfig, userStateOfName } = params
+ const { mcpConfigFileJSON, userStateOfName } = params
// Get all prevServers
- const prevServers = { ...this.clients }
+ const prevServers = { ...this.infoOfClientId }
// Handle config file setup and changes
- const { mcpServers } = mcpConfig
+ const { mcpServers } = mcpConfigFileJSON
const serverNames = Object.keys(mcpServers)
- if (serverNames.length === 0) {
- // TODO: CHANGE THIS TO AN ERROR EVENT
- console.log('No MCP servers found in config file.')
- return
- }
+
const getPrevAndNewServerConfig = (serverName: string) => {
const prevMCPConfig = prevServers[serverName]?.mcpConfig
const newMCPConfig = mcpServers[serverName]
@@ -188,7 +184,7 @@ export class MCPChannel implements IServerChannel {
if (updatedServers.length > 0) {
// emit updated servers
const updatePromises = updatedServers.map(async (serverName) => {
- const prevServer = this.clients[serverName]?.formattedServer;
+ const prevServer = this.infoOfClientId[serverName]?.mcpServer;
const newServer = await this._safeSetupServer(mcpServers[serverName], serverName, userStateOfName[serverName]?.isOn)
return {
type: 'update',
@@ -204,9 +200,9 @@ export class MCPChannel implements IServerChannel {
if (deletedServers.length > 0) {
// emit deleted servers
const deletePromises = deletedServers.map(async (serverName) => {
- const prevServer = this.clients[serverName]?.formattedServer;
- await this._closeServer(serverName)
- this._removeServer(serverName)
+ const prevServer = this.infoOfClientId[serverName]?.mcpServer;
+ await this._closeClient(serverName)
+ this._removeClient(serverName)
return {
type: 'delete',
prevServer,
@@ -218,12 +214,12 @@ export class MCPChannel implements IServerChannel {
}
}
- private async _callSetupServer(server: MCPConfigFileServerType, serverName: string, isOn = true) {
+ private async _callSetupServer(server: MCPConfigFileEntryJSON, serverName: string, isOn = true) {
const clientConfig = getClientConfig(serverName)
const client = new Client(clientConfig)
let transport: Transport;
- let formattedServer: MCPServerModel;
+ let formattedServer: MCPServer;
if (server.url) {
// first try HTTP, fall back to SSE
@@ -279,12 +275,12 @@ export class MCPChannel implements IServerChannel {
}
- this.clients[serverName] = { client, mcpConfig: server, formattedServer }
+ this.infoOfClientId[serverName] = { _client: client, mcpConfig: server, mcpServer: formattedServer }
return formattedServer;
}
// Helper function to safely setup a server
- private async _safeSetupServer(serverConfig: MCPConfigFileServerType, serverName: string, isOn = true) {
+ private async _safeSetupServer(serverConfig: MCPConfigFileEntryJSON, serverName: string, isOn = true) {
try {
return await this._callSetupServer(serverConfig, serverName, isOn)
} catch (err) {
@@ -296,17 +292,17 @@ export class MCPChannel implements IServerChannel {
fullCommand = `${serverConfig.command} ${serverConfig.args?.join(' ') || ''}`
}
- const formattedError: MCPServerErrorModel = {
+ const formattedError: MCPServerError = {
status: 'error',
- tools: [],
+ // tools: [],
error: typedErr.message,
command: fullCommand,
}
// Add the error to the clients object
- this.clients[serverName] = {
+ this.infoOfClientId[serverName] = {
mcpConfig: serverConfig,
- formattedServer: formattedError,
+ mcpServer: formattedError,
}
return formattedError
@@ -314,39 +310,38 @@ export class MCPChannel implements IServerChannel {
}
private async _closeAllMCPServers() {
- for (const serverName in this.clients) {
- await this._closeServer(serverName)
- this._removeServer(serverName)
+ for (const serverName in this.infoOfClientId) {
+ await this._closeClient(serverName)
+ this._removeClient(serverName)
}
console.log('Closed all MCP servers');
}
- private async _closeServer(serverName: string) {
- const server = this.clients[serverName]
- if (server) {
- const { client } = server
- if (client) {
- await client.close()
- }
- // Remove the client from the clients object
- delete this.clients[serverName].client
- console.log(`Closed MCP server ${serverName}`);
+ private async _closeClient(serverName: string) {
+ const info = this.infoOfClientId[serverName]
+ if (!info) return
+ const { _client: client } = info
+ if (client) {
+ await client.close()
}
+ // Remove the client from the clients object
+ delete this.infoOfClientId[serverName]._client
+ console.log(`Closed MCP server ${serverName}`);
}
- private _removeServer(serverName: string) {
- if (this.clients[serverName]) {
- delete this.clients[serverName]
+ private _removeClient(serverName: string) {
+ if (this.infoOfClientId[serverName]) {
+ delete this.infoOfClientId[serverName]
console.log(`Removed MCP server ${serverName}`);
}
}
private async _toggleMCPServer(serverName: string, isOn: boolean) {
- const prevServer = this.clients[serverName]?.formattedServer
+ const prevServer = this.infoOfClientId[serverName]?.mcpServer
if (isOn) {
// Handle turning on the server
// this.mcpEmitters.serverEvent.onChangeLoading.fire(getLoadingServerObject(serverName, isOn))
- const formattedServer = await this._callSetupServer(this.clients[serverName].mcpConfig, serverName)
+ const formattedServer = await this._callSetupServer(this.infoOfClientId[serverName].mcpConfig, serverName)
this.mcpEmitters.serverEvent.onUpdate.fire({
response: {
type: 'update',
@@ -358,7 +353,7 @@ export class MCPChannel implements IServerChannel {
} else {
// Handle turning off the server
// this.mcpEmitters.serverEvent.onChangeLoading.fire(getLoadingServerObject(serverName, isOn))
- this._closeServer(serverName)
+ this._closeClient(serverName)
this.mcpEmitters.serverEvent.onUpdate.fire({
response: {
type: 'update',
@@ -380,9 +375,9 @@ export class MCPChannel implements IServerChannel {
// tool call functions
private async _callTool(serverName: string, toolName: string, params: any): Promise {
- const server = this.clients[serverName]
+ const server = this.infoOfClientId[serverName]
if (!server) throw new Error(`Server ${serverName} not found`)
- const { client } = server
+ const { _client: client } = server
if (!client) throw new Error(`Client for server ${serverName} not found`)
// Call the tool with the provided parameters