mirror of
https://github.com/voideditor/void
synced 2026-05-23 17:38:23 +00:00
updates/fixes to mcp
This commit is contained in:
parent
f5b0f24445
commit
7aacbca580
11 changed files with 199 additions and 317 deletions
|
|
@ -16,7 +16,7 @@ import { AnthropicReasoning, getErrorMessage, RawToolCallObj, RawToolParamsObj }
|
|||
import { generateUuid } from '../../../../base/common/uuid.js';
|
||||
import { FeatureName, ModelSelection, ModelSelectionOptions } from '../common/voidSettingsTypes.js';
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||
import { approvalTypeOfToolName, ToolCallParams, ToolResultType } from '../common/toolsServiceTypes.js';
|
||||
import { approvalTypeOfToolName, BuiltinToolCallParams, BuiltinToolResultType } from '../common/toolsServiceTypes.js';
|
||||
import { IToolsService } from './toolsService.js';
|
||||
import { CancellationToken } from '../../../../base/common/cancellation.js';
|
||||
import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
|
||||
|
|
@ -181,7 +181,7 @@ export type ThreadStreamState = {
|
|||
llmInfo?: undefined;
|
||||
toolInfo: {
|
||||
toolName: ToolName;
|
||||
toolParams: ToolCallParams[ToolName];
|
||||
toolParams: BuiltinToolCallParams[ToolName];
|
||||
id: string;
|
||||
content: string;
|
||||
rawParams: RawToolParamsObj;
|
||||
|
|
@ -532,7 +532,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
const lastMsg = thread.messages[thread.messages.length - 1]
|
||||
|
||||
let params: ToolCallParams[ToolName]
|
||||
let params: BuiltinToolCallParams[ToolName]
|
||||
if (lastMsg.role === 'tool' && lastMsg.type !== 'invalid_params') {
|
||||
params = lastMsg.params
|
||||
}
|
||||
|
|
@ -597,12 +597,12 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
threadId: string,
|
||||
toolName: ToolName,
|
||||
toolId: string,
|
||||
opts: { preapproved: true, unvalidatedToolParams: RawToolParamsObj, validatedParams: ToolCallParams[ToolName] } | { preapproved: false, unvalidatedToolParams: RawToolParamsObj },
|
||||
opts: { preapproved: true, unvalidatedToolParams: RawToolParamsObj, validatedParams: BuiltinToolCallParams[ToolName] } | { preapproved: false, unvalidatedToolParams: RawToolParamsObj },
|
||||
): Promise<{ awaitingUserApproval?: boolean, interrupted?: boolean }> => {
|
||||
|
||||
// compute these below
|
||||
let toolParams: ToolCallParams[ToolName]
|
||||
let toolResult: Awaited<ToolResultType[typeof toolName]>
|
||||
let toolParams: BuiltinToolCallParams[ToolName]
|
||||
let toolResult: Awaited<BuiltinToolResultType[typeof toolName]>
|
||||
let toolResultStr: string
|
||||
|
||||
if (!opts.preapproved) { // skip this if pre-approved
|
||||
|
|
@ -616,8 +616,8 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
return {}
|
||||
}
|
||||
// once validated, add checkpoint for edit
|
||||
if (toolName === 'edit_file') { this._addToolEditCheckpoint({ threadId, uri: (toolParams as ToolCallParams['edit_file']).uri }) }
|
||||
if (toolName === 'rewrite_file') { this._addToolEditCheckpoint({ threadId, uri: (toolParams as ToolCallParams['rewrite_file']).uri }) }
|
||||
if (toolName === 'edit_file') { this._addToolEditCheckpoint({ threadId, uri: (toolParams as BuiltinToolCallParams['edit_file']).uri }) }
|
||||
if (toolName === 'rewrite_file') { this._addToolEditCheckpoint({ threadId, uri: (toolParams as BuiltinToolCallParams['rewrite_file']).uri }) }
|
||||
|
||||
// 2. if tool requires approval, break from the loop, awaiting approval
|
||||
|
||||
|
|
@ -638,6 +638,33 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
|
||||
|
||||
// TODO!!!!!!!!!
|
||||
// const isBuiltInTool = (toolNames as string[]).includes(toolName)
|
||||
// const callToolFn = (toolName: string, toolParams: BuiltinToolCallParams[ToolName]) => {
|
||||
// if (isBuiltInTool) {
|
||||
|
||||
|
||||
// }
|
||||
// else {
|
||||
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// const stringifyToolFn = (toolName: string, toolResult: Awaited<BuiltinToolResultType[ToolName]>) => {
|
||||
// if (isBuiltInTool) {
|
||||
|
||||
|
||||
// }
|
||||
// else {
|
||||
// if (result.event === 'error' || result.event === 'text') {
|
||||
// return result.text;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// 3. call the tool
|
||||
// this._setStreamState(threadId, { isRunning: 'tool' }, 'merge')
|
||||
const runningTool = { role: 'tool', type: 'running_now', name: toolName, params: toolParams, content: '(value not received yet...)', result: null, id: toolId, rawParams: opts.unvalidatedToolParams } as const
|
||||
|
|
@ -1300,7 +1327,7 @@ We only need to do it for files that were edited since `from`, ie files between
|
|||
}
|
||||
// URIs of files that have been read
|
||||
else if (m.role === 'tool' && m.type === 'success' && m.name === 'read_file') {
|
||||
const params = m.params as ToolCallParams['read_file']
|
||||
const params = m.params as BuiltinToolCallParams['read_file']
|
||||
addURI(params.uri)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -905,11 +905,6 @@ const MCPServerComponent = ({ name, server }: { name: string, server: MCPServer
|
|||
const accessor = useAccessor();
|
||||
const mcpService = accessor.get('IMCPService');
|
||||
|
||||
const handleChangeEvent = (e: boolean) => {
|
||||
// Handle the change event
|
||||
mcpService.toggleMCPServer(name, e);
|
||||
}
|
||||
|
||||
const voidSettings = useSettingsState()
|
||||
const isOn = voidSettings.mcpUserStateOfName[name]?.isOn
|
||||
|
||||
|
|
@ -934,7 +929,7 @@ const MCPServerComponent = ({ name, server }: { name: string, server: MCPServer
|
|||
<VoidSwitch
|
||||
value={isOn ?? false}
|
||||
disabled={server.status === 'error'}
|
||||
onChange={handleChangeEvent}
|
||||
onChange={() => mcpService.toggleServerIsOn(name, !isOn)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { QueryBuilder } from '../../../services/search/common/queryBuilder.js'
|
|||
import { ISearchService } from '../../../services/search/common/search.js'
|
||||
import { IEditCodeService } from './editCodeServiceInterface.js'
|
||||
import { ITerminalToolService } from './terminalToolService.js'
|
||||
import { LintErrorItem, ToolCallParams, ToolResultType } from '../common/toolsServiceTypes.js'
|
||||
import { LintErrorItem, BuiltinToolCallParams, BuiltinToolResultType } from '../common/toolsServiceTypes.js'
|
||||
import { IVoidModelService } from '../common/voidModelService.js'
|
||||
import { EndOfLinePreference } from '../../../../editor/common/model.js'
|
||||
import { IVoidCommandBarService } from './voidCommandBarService.js'
|
||||
|
|
@ -19,22 +19,12 @@ import { RawToolParamsObj } from '../common/sendLLMMessageTypes.js'
|
|||
import { MAX_CHILDREN_URIs_PAGE, MAX_FILE_CHARS_PAGE, MAX_TERMINAL_BG_COMMAND_TIME, MAX_TERMINAL_INACTIVE_TIME, ToolName } from '../common/prompt/prompts.js'
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js'
|
||||
import { generateUuid } from '../../../../base/common/uuid.js'
|
||||
import { IMCPService, MCPCallTool, MCPToolResultToString } from '../common/mcpService.js'
|
||||
|
||||
|
||||
// tool use for AI
|
||||
|
||||
|
||||
|
||||
|
||||
type ValidateParams = { [T in ToolName]: (p: RawToolParamsObj) => ToolCallParams[T] }
|
||||
type CallTool = { [T in ToolName]: (p: ToolCallParams[T]) => Promise<{ result: ToolResultType[T] | Promise<ToolResultType[T]>, interruptTool?: () => void }> }
|
||||
type ToolResultToString = { [T in ToolName]: (p: ToolCallParams[T], result: Awaited<ToolResultType[T]>) => string }
|
||||
|
||||
// Interfaces that accept both internal tools and MCP tools
|
||||
export type ToolHandler = CallTool & MCPCallTool;
|
||||
export type ToolResultToStringHandler = ToolResultToString & MCPToolResultToString
|
||||
|
||||
type ValidateBuiltinParams = { [T in ToolName]: (p: RawToolParamsObj) => BuiltinToolCallParams[T] }
|
||||
type CallBuiltinTool = { [T in ToolName]: (p: BuiltinToolCallParams[T]) => Promise<{ result: BuiltinToolResultType[T] | Promise<BuiltinToolResultType[T]>, interruptTool?: () => void }> }
|
||||
type BuiltinToolResultToString = { [T in ToolName]: (p: BuiltinToolCallParams[T], result: Awaited<BuiltinToolResultType[T]>) => string }
|
||||
|
||||
|
||||
const isFalsy = (u: unknown) => {
|
||||
|
|
@ -115,9 +105,9 @@ const checkIfIsFolder = (uriStr: string) => {
|
|||
|
||||
export interface IToolsService {
|
||||
readonly _serviceBrand: undefined;
|
||||
validateParams: ValidateParams;
|
||||
callTool: ToolHandler;
|
||||
stringOfResult: ToolResultToStringHandler;
|
||||
validateParams: ValidateBuiltinParams;
|
||||
callTool: CallBuiltinTool;
|
||||
stringOfResult: BuiltinToolResultToString;
|
||||
}
|
||||
|
||||
export const IToolsService = createDecorator<IToolsService>('ToolsService');
|
||||
|
|
@ -126,9 +116,9 @@ export class ToolsService implements IToolsService {
|
|||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
public validateParams: ValidateParams;
|
||||
public callTool: ToolHandler;
|
||||
public stringOfResult: ToolResultToStringHandler;
|
||||
public validateParams: ValidateBuiltinParams;
|
||||
public callTool: CallBuiltinTool;
|
||||
public stringOfResult: BuiltinToolResultToString;
|
||||
|
||||
constructor(
|
||||
@IFileService fileService: IFileService,
|
||||
|
|
@ -142,7 +132,6 @@ export class ToolsService implements IToolsService {
|
|||
@IDirectoryStrService private readonly directoryStrService: IDirectoryStrService,
|
||||
@IMarkerService private readonly markerService: IMarkerService,
|
||||
@IVoidSettingsService private readonly voidSettingsService: IVoidSettingsService,
|
||||
@IMCPService private readonly mcpService: IMCPService,
|
||||
) {
|
||||
|
||||
const queryBuilder = instantiationService.createInstance(QueryBuilder);
|
||||
|
|
@ -452,8 +441,6 @@ export class ToolsService implements IToolsService {
|
|||
await this.terminalToolService.killPersistentTerminal(persistentTerminalId)
|
||||
return { result: {} }
|
||||
},
|
||||
// Returns MCP server call tool functions
|
||||
...this.mcpService.getMCPToolFns().callTool,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -557,8 +544,6 @@ export class ToolsService implements IToolsService {
|
|||
kill_persistent_terminal: (params, _result) => {
|
||||
return `Successfully closed terminal "${params.persistentTerminalId}".`;
|
||||
},
|
||||
// All MCP server result to string functions
|
||||
...this.mcpService.getMCPToolFns().resultToString,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { URI } from '../../../../base/common/uri.js';
|
|||
import { VoidFileSnapshot } from './editCodeServiceTypes.js';
|
||||
import { ToolName } from './prompt/prompts.js';
|
||||
import { AnthropicReasoning, RawToolParamsObj } from './sendLLMMessageTypes.js';
|
||||
import { ToolCallParams, ToolResultType } from './toolsServiceTypes.js';
|
||||
import { BuiltinToolCallParams, BuiltinToolResultType } from './toolsServiceTypes.js';
|
||||
|
||||
export type ToolMessage<T extends ToolName> = {
|
||||
role: 'tool';
|
||||
|
|
@ -18,13 +18,13 @@ export type ToolMessage<T extends ToolName> = {
|
|||
// in order of events:
|
||||
| { type: 'invalid_params', result: null, name: T, }
|
||||
|
||||
| { type: 'tool_request', result: null, name: T, params: ToolCallParams[T], } // params were validated, awaiting user
|
||||
| { type: 'tool_request', result: null, name: T, params: BuiltinToolCallParams[T], } // params were validated, awaiting user
|
||||
|
||||
| { type: 'running_now', result: null, name: T, params: ToolCallParams[T], }
|
||||
| { type: 'running_now', result: null, name: T, params: BuiltinToolCallParams[T], }
|
||||
|
||||
| { type: 'tool_error', result: string, name: T, params: ToolCallParams[T], } // error when tool was running
|
||||
| { type: 'success', result: Awaited<ToolResultType[T]>, name: T, params: ToolCallParams[T], }
|
||||
| { type: 'rejected', result: null, name: T, params: ToolCallParams[T] }
|
||||
| { type: 'tool_error', result: string, name: T, params: BuiltinToolCallParams[T], } // error when tool was running
|
||||
| { type: 'success', result: Awaited<BuiltinToolResultType[T]>, name: T, params: BuiltinToolCallParams[T], }
|
||||
| { type: 'rejected', result: null, name: T, params: BuiltinToolCallParams[T] }
|
||||
) // user rejected
|
||||
|
||||
export type DecorativeCanceledTool = {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { registerSingleton, InstantiationType } from '../../../../platform/insta
|
|||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IFileService, IFileStat } from '../../../../platform/files/common/files.js';
|
||||
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
|
||||
import { ShallowDirectoryItem, ToolCallParams, ToolResultType } from './toolsServiceTypes.js';
|
||||
import { ShallowDirectoryItem, BuiltinToolCallParams, BuiltinToolResultType } from './toolsServiceTypes.js';
|
||||
import { MAX_CHILDREN_URIs_PAGE, MAX_DIRSTR_CHARS_TOTAL_BEGINNING, MAX_DIRSTR_CHARS_TOTAL_TOOL } from './prompt/prompts.js';
|
||||
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ export const computeDirectoryTree1Deep = async (
|
|||
fileService: IFileService,
|
||||
rootURI: URI,
|
||||
pageNumber: number = 1,
|
||||
): Promise<ToolResultType['ls_dir']> => {
|
||||
): Promise<BuiltinToolResultType['ls_dir']> => {
|
||||
const stat = await fileService.resolve(rootURI, { resolveMetadata: false });
|
||||
if (!stat.isDirectory) {
|
||||
return { children: null, hasNextPage: false, hasPrevPage: false, itemsRemaining: 0 };
|
||||
|
|
@ -107,7 +107,7 @@ export const computeDirectoryTree1Deep = async (
|
|||
};
|
||||
};
|
||||
|
||||
export const stringifyDirectoryTree1Deep = (params: ToolCallParams['ls_dir'], result: ToolResultType['ls_dir']): string => {
|
||||
export const stringifyDirectoryTree1Deep = (params: BuiltinToolCallParams['ls_dir'], result: BuiltinToolResultType['ls_dir']): string => {
|
||||
if (!result.children) {
|
||||
return `Error: ${params.uri} is not a directory`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,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 { MCPServerOfName, MCPConfigFileJSON, MCPAddServerResponse, MCPUpdateServerResponse, MCPDeleteServerResponse, MCPServer, MCPToolCallParams, MCPGenericToolResponse } from './mcpServiceTypes.js';
|
||||
import { MCPServerOfName, MCPConfigFileJSON, MCPServer, MCPToolCallParams, MCPGenericToolResponse, MCPServerEventResponse } from './mcpServiceTypes.js';
|
||||
import { Event, Emitter } from '../../../../base/common/event.js';
|
||||
import { InternalToolInfo } from './prompt/prompts.js';
|
||||
import { IVoidSettingsService } from './voidSettingsService.js';
|
||||
|
|
@ -29,16 +29,16 @@ type MCPServiceState = {
|
|||
export interface IMCPService {
|
||||
readonly _serviceBrand: undefined;
|
||||
revealMCPConfigFile(): Promise<void>;
|
||||
toggleMCPServer(serverName: string, isOn: boolean): Promise<void>;
|
||||
toggleServerIsOn(serverName: string, isOn: boolean): Promise<void>;
|
||||
|
||||
readonly state: MCPServiceState; // NOT persisted
|
||||
onDidChangeState: Event<void>;
|
||||
|
||||
getCurrentMCPToolNames(): InternalToolInfo[];
|
||||
getMCPToolFns(): {
|
||||
callTool: MCPCallTool;
|
||||
resultToString: MCPToolResultToString
|
||||
};
|
||||
|
||||
// TODO!!!!!!!!! getMCPToolDescriptors (the equivalent of tools in prompts.ts)
|
||||
|
||||
// getMCPToolFns(): MCPCallToolOfToolName;
|
||||
}
|
||||
|
||||
export const IMCPService = createDecorator<IMCPService>('mcpConfigService');
|
||||
|
|
@ -50,21 +50,13 @@ const MCP_CONFIG_SAMPLE = { mcpServers: {} }
|
|||
const MCP_CONFIG_SAMPLE_STRING = JSON.stringify(MCP_CONFIG_SAMPLE, null, 2);
|
||||
|
||||
|
||||
export interface MCPCallTool {
|
||||
export interface MCPCallToolOfToolName {
|
||||
[toolName: string]: (params: any) => Promise<{
|
||||
result: any | Promise<any>,
|
||||
interruptTool?: () => void
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface MCPToolResultToString {
|
||||
[toolName: string]: (params: any, result: any) => string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class MCPService extends Disposable implements IMCPService {
|
||||
_serviceBrand: undefined;
|
||||
|
|
@ -96,9 +88,9 @@ class MCPService extends Disposable implements IMCPService {
|
|||
super();
|
||||
this.channel = this.mainProcessService.getChannel('void-channel-mcp')
|
||||
|
||||
this._register((this.channel.listen('onAdd_server') satisfies Event<MCPAddServerResponse>)(e => { this._setMCPServerState(e.response.name, e.response.newServer) }));
|
||||
this._register((this.channel.listen('onUpdate_server') satisfies Event<MCPUpdateServerResponse>)(e => { this._setMCPServerState(e.response.name, e.response.newServer) }));
|
||||
this._register((this.channel.listen('onDelete_server') satisfies Event<MCPDeleteServerResponse>)(e => { this._setMCPServerState(e.response.name, e.response.newServer) }));
|
||||
this._register((this.channel.listen('onAdd_server') satisfies Event<MCPServerEventResponse>)(e => { this._setMCPServerState(e.response.name, e.response.newServer) }));
|
||||
this._register((this.channel.listen('onUpdate_server') satisfies Event<MCPServerEventResponse>)(e => { this._setMCPServerState(e.response.name, e.response.newServer) }));
|
||||
this._register((this.channel.listen('onDelete_server') satisfies Event<MCPServerEventResponse>)(e => { this._setMCPServerState(e.response.name, e.response.newServer) }));
|
||||
|
||||
this._initialize();
|
||||
}
|
||||
|
|
@ -205,9 +197,9 @@ class MCPService extends Disposable implements IMCPService {
|
|||
}
|
||||
|
||||
// toggle MCP server and update isOn in void settings
|
||||
public async toggleMCPServer(serverName: string, isOn: boolean): Promise<void> {
|
||||
this.channel.call('toggleMCPServer', { serverName, isOn })
|
||||
public async toggleServerIsOn(serverName: string, isOn: boolean): Promise<void> {
|
||||
await this.voidSettingsService.setMCPServerState(serverName, { isOn });
|
||||
this.channel.call('toggleMCPServer', { serverName, isOn })
|
||||
}
|
||||
|
||||
// utility functions
|
||||
|
|
@ -272,66 +264,55 @@ class MCPService extends Disposable implements IMCPService {
|
|||
await this.voidSettingsService.removeMCPUserStateOfNames(removedServerNames);
|
||||
|
||||
// set all servers to loading
|
||||
const mcpConfigOfName = newConfigFileJSON.mcpServers
|
||||
for (const serverName in mcpConfigOfName) {
|
||||
for (const serverName in newConfigFileJSON.mcpServers) {
|
||||
if (serverName in this.state.mcpServerOfName) continue
|
||||
this._setMCPServerState(serverName, {
|
||||
status: 'loading',
|
||||
tools: [],
|
||||
})
|
||||
}
|
||||
const updatedServerNames = Object.keys(newConfigFileJSON.mcpServers).filter(serverName => !addedServerNames.includes(serverName) && !removedServerNames.includes(serverName))
|
||||
|
||||
this.channel.call('refreshMCPServers', { mcpConfigFileJSON: newConfigFileJSON, userStateOfName: this.voidSettingsService.state.mcpUserStateOfName })
|
||||
this.channel.call('refreshMCPServers', {
|
||||
mcpConfigFileJSON: newConfigFileJSON,
|
||||
addedServerNames,
|
||||
removedServerNames,
|
||||
updatedServerNames,
|
||||
userStateOfName: this.voidSettingsService.state.mcpUserStateOfName,
|
||||
})
|
||||
}
|
||||
|
||||
public async callMCPTool(toolData: MCPToolCallParams): Promise<MCPGenericToolResponse> {
|
||||
const response = await this.channel.call<MCPGenericToolResponse>('callTool', toolData);
|
||||
return response;
|
||||
|
||||
public async callMCPTool(toolData: MCPToolCallParams): Promise<{ result: MCPGenericToolResponse }> {
|
||||
const result = await this.channel.call<MCPGenericToolResponse>('callTool', toolData);
|
||||
return { result };
|
||||
}
|
||||
|
||||
public getMCPToolFns(): { callTool: MCPCallTool; resultToString: MCPToolResultToString } {
|
||||
const tools = this.getCurrentMCPToolNames();
|
||||
const toolFns: MCPCallTool = {};
|
||||
const toolResultToStringFns: MCPToolResultToString = {};
|
||||
// public getMCPToolFns(): MCPToolResultType {
|
||||
// const tools = this.getCurrentMCPToolNames();
|
||||
// const toolFns: MCPToolResultType = {};
|
||||
|
||||
tools.forEach((tool) => {
|
||||
const name = tool.name;
|
||||
const serverName = tool.mcpServerName;
|
||||
// tools.forEach((tool) => {
|
||||
// const name = tool.name;
|
||||
// // Define the tool call function
|
||||
// const toolFn = async (params: {
|
||||
// serverName: string,
|
||||
// toolName: string,
|
||||
// args: any
|
||||
// }) => {
|
||||
// const { serverName, toolName, args } = params;
|
||||
// const response = await this.callMCPTool({
|
||||
// serverName,
|
||||
// toolName,
|
||||
// params: args,
|
||||
// });
|
||||
// return { result: response }
|
||||
// };
|
||||
// toolFns[name] = toolFn;
|
||||
// });
|
||||
|
||||
// Define the tool call function
|
||||
const toolFn = async (params: {
|
||||
serverName: string,
|
||||
toolName: string,
|
||||
args: any
|
||||
}) => {
|
||||
const { serverName, toolName, args } = params;
|
||||
const response = await this.callMCPTool({
|
||||
serverName,
|
||||
toolName,
|
||||
params: args,
|
||||
});
|
||||
return {
|
||||
result: response,
|
||||
};
|
||||
};
|
||||
|
||||
// Define the result-to-string function
|
||||
const resultToStringFn = (params: any, result: MCPGenericToolResponse): string => {
|
||||
if (result.event === 'error' || result.event === 'text') {
|
||||
return result.text;
|
||||
}
|
||||
throw new Error(`MCP Server ${serverName} and Tool ${name} returned an unexpected result: ${JSON.stringify(result)}`);
|
||||
};
|
||||
|
||||
toolFns[name] = toolFn;
|
||||
toolResultToStringFns[name] = resultToStringFn;
|
||||
});
|
||||
|
||||
return {
|
||||
callTool: toolFns,
|
||||
resultToString: toolResultToStringFns
|
||||
};
|
||||
}
|
||||
// return toolFns
|
||||
// }
|
||||
}
|
||||
|
||||
registerSingleton(IMCPService, MCPService, InstantiationType.Eager);
|
||||
|
|
|
|||
|
|
@ -152,36 +152,12 @@ export interface MCPServerOfName {
|
|||
}
|
||||
|
||||
export type MCPServerEvent = {
|
||||
type: 'add';
|
||||
name: string;
|
||||
prevServer?: undefined;
|
||||
newServer: MCPServer;
|
||||
} | {
|
||||
type: 'update';
|
||||
name: string;
|
||||
prevServer: MCPServer;
|
||||
newServer: MCPServer;
|
||||
} | {
|
||||
type: 'delete';
|
||||
name: string;
|
||||
newServer?: undefined;
|
||||
prevServer: MCPServer;
|
||||
} | {
|
||||
type: 'loading';
|
||||
name: string;
|
||||
prevServer?: undefined;
|
||||
newServer: MCPServer;
|
||||
prevServer?: MCPServer;
|
||||
newServer?: MCPServer;
|
||||
}
|
||||
|
||||
// Response types
|
||||
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 interface MCPConfigFileParseErrorResponse {
|
||||
response: {
|
||||
type: 'config-file-error';
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { IDirectoryStrService } from '../directoryStrService.js';
|
|||
import { StagingSelectionItem } from '../chatThreadServiceTypes.js';
|
||||
import { os } from '../helpers/systemInfo.js';
|
||||
import { RawToolParamsObj } from '../sendLLMMessageTypes.js';
|
||||
import { approvalTypeOfToolName, ToolCallParams, ToolResultType } from '../toolsServiceTypes.js';
|
||||
import { approvalTypeOfToolName, BuiltinToolCallParams, BuiltinToolResultType } from '../toolsServiceTypes.js';
|
||||
import { ChatMode } from '../voidSettingsTypes.js';
|
||||
|
||||
// Triple backtick wrapper used throughout the prompts for code blocks
|
||||
|
|
@ -186,11 +186,11 @@ export type SnakeCaseKeys<T extends Record<string, any>> = {
|
|||
// export const voidTools = {
|
||||
export const voidTools
|
||||
: {
|
||||
[T in keyof ToolCallParams]: {
|
||||
[T in keyof BuiltinToolCallParams]: {
|
||||
name: string;
|
||||
description: string;
|
||||
// more params can be generated than exist here, but these params must be a subset of them
|
||||
params: Partial<{ [paramName in keyof SnakeCaseKeys<ToolCallParams[T]>]: { description: string } }>
|
||||
params: Partial<{ [paramName in keyof SnakeCaseKeys<BuiltinToolCallParams[T]>]: { description: string } }>
|
||||
}
|
||||
}
|
||||
= {
|
||||
|
|
@ -345,10 +345,10 @@ export const voidTools
|
|||
// go_to_definition
|
||||
// go_to_usages
|
||||
|
||||
} satisfies { [T in keyof ToolResultType]: InternalToolInfo }
|
||||
} satisfies { [T in keyof BuiltinToolResultType]: InternalToolInfo }
|
||||
|
||||
|
||||
export type ToolName = keyof ToolResultType
|
||||
export type ToolName = keyof BuiltinToolResultType
|
||||
export const toolNames = Object.keys(voidTools) as ToolName[]
|
||||
|
||||
type ToolParamNameOfTool<T extends ToolName> = keyof (typeof voidTools)[T]['params']
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export const toolApprovalTypes = new Set<ToolApprovalType>(
|
|||
)
|
||||
|
||||
// PARAMS OF TOOL CALL
|
||||
export type ToolCallParams = {
|
||||
export type BuiltinToolCallParams = {
|
||||
'read_file': { uri: URI, startLine: number | null, endLine: number | null, pageNumber: number },
|
||||
'ls_dir': { uri: URI, pageNumber: number },
|
||||
'get_dir_tree': { uri: URI },
|
||||
|
|
@ -58,7 +58,7 @@ export type ToolCallParams = {
|
|||
}
|
||||
|
||||
// RESULT OF TOOL CALL
|
||||
export type ToolResultType = {
|
||||
export type BuiltinToolResultType = {
|
||||
'read_file': { fileContents: string, totalFileLen: number, totalNumLines: number, hasNextPage: boolean },
|
||||
'ls_dir': { children: ShallowDirectoryItem[] | null, hasNextPage: boolean, hasPrevPage: boolean, itemsRemaining: number },
|
||||
'get_dir_tree': { str: string, },
|
||||
|
|
|
|||
|
|
@ -580,10 +580,9 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
|
|||
}
|
||||
|
||||
setMCPServerState = async (serverName: string, state: MCPUserState) => {
|
||||
const { mcpUserStateOfName: mcpServerStates } = this.state
|
||||
if (!(serverName in mcpServerStates)) return // if not in list, do nothing
|
||||
const { mcpUserStateOfName } = this.state
|
||||
const newMCPServerStates = {
|
||||
...mcpServerStates,
|
||||
...mcpUserStateOfName,
|
||||
[serverName]: state,
|
||||
}
|
||||
await this._setMCPUserStateOfName(newMCPServerStates)
|
||||
|
|
|
|||
|
|
@ -13,10 +13,9 @@ 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 { MCPConfigFileJSON, MCPConfigFileEntryJSON, MCPServer, MCPAddServerResponse, MCPUpdateServerResponse, MCPDeleteServerResponse, MCPGenericToolResponse, MCPToolErrorResponse } from '../common/mcpServiceTypes.js';
|
||||
import { MCPConfigFileJSON, MCPConfigFileEntryJSON, MCPServer, MCPGenericToolResponse, MCPToolErrorResponse, MCPServerEventResponse } 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 getClientConfig = (serverName: string) => {
|
||||
|
|
@ -27,53 +26,46 @@ const getClientConfig = (serverName: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
type MCPServerNonError = MCPServer & { status: Omit<MCPServer['status'], 'error'> }
|
||||
type MCPServerError = MCPServer & { status: 'error' }
|
||||
|
||||
|
||||
|
||||
type ClientInfo = {
|
||||
_client: Client, // _client is the client that connects with an mcp client. We're calling mcp clients "server" everywhere except here for naming consistency.
|
||||
mcpServerEntryJSON: MCPConfigFileEntryJSON,
|
||||
mcpServer: MCPServerNonError,
|
||||
} | {
|
||||
_client?: undefined,
|
||||
mcpServerEntryJSON: MCPConfigFileEntryJSON,
|
||||
mcpServer: MCPServerError,
|
||||
}
|
||||
|
||||
type InfoOfClientId = {
|
||||
[clientId: string]: ClientInfo
|
||||
}
|
||||
|
||||
export class MCPChannel implements IServerChannel {
|
||||
|
||||
// connected clients
|
||||
private infoOfClientId: {
|
||||
[clientId: string]: {
|
||||
_client: Client,
|
||||
mcpConfig: MCPConfigFileEntryJSON,
|
||||
mcpServer: MCPServerNonError,
|
||||
} | {
|
||||
_client?: undefined,
|
||||
mcpConfig: MCPConfigFileEntryJSON,
|
||||
mcpServer: MCPServerError,
|
||||
}
|
||||
} = {}
|
||||
private readonly infoOfClientId: InfoOfClientId = {}
|
||||
private readonly _refreshingServerNames: Set<string> = new Set()
|
||||
|
||||
// mcp emitters
|
||||
private readonly mcpEmitters = {
|
||||
serverEvent: {
|
||||
onAdd: new Emitter<MCPAddServerResponse>(),
|
||||
onUpdate: new Emitter<MCPUpdateServerResponse>(),
|
||||
onDelete: new Emitter<MCPDeleteServerResponse>(),
|
||||
// onResult: new Emitter<>(),
|
||||
// onError: new Emitter<>(),
|
||||
// onChangeLoading: new Emitter<MCPLoadingResponse>(), // really onStart
|
||||
onAdd: new Emitter<MCPServerEventResponse>(),
|
||||
onUpdate: new Emitter<MCPServerEventResponse>(),
|
||||
onDelete: new Emitter<MCPServerEventResponse>(),
|
||||
}
|
||||
// toolCall: {
|
||||
// success: new Emitter<void>(),
|
||||
// error: new Emitter<void>(),
|
||||
// },
|
||||
} satisfies {
|
||||
serverEvent: {
|
||||
onAdd: Emitter<MCPAddServerResponse>,
|
||||
onUpdate: Emitter<MCPUpdateServerResponse>,
|
||||
onDelete: Emitter<MCPDeleteServerResponse>,
|
||||
// onChangeLoading: Emitter<MCPLoadingResponse>,
|
||||
onAdd: Emitter<MCPServerEventResponse>,
|
||||
onUpdate: Emitter<MCPServerEventResponse>,
|
||||
onDelete: Emitter<MCPServerEventResponse>,
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
// private readonly metricsService: IMetricsService,
|
||||
) { }
|
||||
|
||||
// browser uses this to listen for changes
|
||||
|
|
@ -118,108 +110,62 @@ export class MCPChannel implements IServerChannel {
|
|||
|
||||
// server functions
|
||||
|
||||
private async _refreshMCPServers(params: { mcpConfigFileJSON: MCPConfigFileJSON, userStateOfName: MCPUserStateOfName }) {
|
||||
|
||||
const { mcpConfigFileJSON, userStateOfName } = params
|
||||
private async _refreshMCPServers(params: { mcpConfigFileJSON: MCPConfigFileJSON, userStateOfName: MCPUserStateOfName, addedServerNames: string[], removedServerNames: string[], updatedServerNames: string[] }) {
|
||||
|
||||
// Get all prevServers
|
||||
const prevServers = { ...this.infoOfClientId }
|
||||
const {
|
||||
mcpConfigFileJSON,
|
||||
userStateOfName,
|
||||
addedServerNames,
|
||||
removedServerNames,
|
||||
updatedServerNames,
|
||||
} = params
|
||||
|
||||
// Handle config file setup and changes
|
||||
const { mcpServers } = mcpConfigFileJSON
|
||||
const serverNames = Object.keys(mcpServers)
|
||||
const { mcpServers: mcpServersJSON } = mcpConfigFileJSON
|
||||
|
||||
const getPrevAndNewServerConfig = (serverName: string) => {
|
||||
const prevMCPConfig = prevServers[serverName]?.mcpConfig
|
||||
const newMCPConfig = mcpServers[serverName]
|
||||
return { prevMCPConfig, newMCPConfig }
|
||||
}
|
||||
const allChanges: { type: 'added' | 'removed' | 'updated', serverName: string }[] = [
|
||||
...addedServerNames.map(n => ({ serverName: n, type: 'added' }) as const),
|
||||
...removedServerNames.map(n => ({ serverName: n, type: 'removed' }) as const),
|
||||
...updatedServerNames.map(n => ({ serverName: n, type: 'updated' }) as const),
|
||||
]
|
||||
|
||||
// Divide the server based on event
|
||||
const addedServers = serverNames.filter((serverName) => {
|
||||
const { prevMCPConfig, newMCPConfig } = getPrevAndNewServerConfig(serverName)
|
||||
const isNew = !prevMCPConfig && newMCPConfig
|
||||
// if (isAdded) {
|
||||
// this.mcpEmitters.serverEvent.onChangeLoading.fire(getLoadingServerObject(serverName, serverStates[serverName]?.isOn))
|
||||
// }
|
||||
return isNew
|
||||
})
|
||||
const updatedServers = serverNames.filter((serverName) => {
|
||||
const { prevMCPConfig, newMCPConfig } = getPrevAndNewServerConfig(serverName)
|
||||
const isNew = prevMCPConfig && newMCPConfig && !equals(prevMCPConfig, newMCPConfig)
|
||||
// if (isUpdated) {
|
||||
// this.mcpEmitters.serverEvent.onChangeLoading.fire(getLoadingServerObject(serverName, serverStates[serverName]?.isOn))
|
||||
// }
|
||||
return isNew
|
||||
})
|
||||
const deletedServers = Object.keys(prevServers).filter((serverName) => {
|
||||
const { prevMCPConfig, newMCPConfig } = getPrevAndNewServerConfig(serverName)
|
||||
const isNew = prevMCPConfig && !newMCPConfig
|
||||
// if (isDeleted) {
|
||||
// this.mcpEmitters.serverEvent.onChangeLoading.fire(getLoadingServerObject(serverName, serverStates[serverName]?.isOn))
|
||||
// }
|
||||
return isNew
|
||||
})
|
||||
await Promise.all(
|
||||
allChanges.map(async ({ serverName, type }) => {
|
||||
|
||||
// Check if no changes were made
|
||||
if (addedServers.length === 0 && updatedServers.length === 0 && deletedServers.length === 0) {
|
||||
console.log('No changes to MCP servers found.')
|
||||
return
|
||||
}
|
||||
// check if already refreshing
|
||||
if (this._refreshingServerNames.has(serverName)) return
|
||||
this._refreshingServerNames.add(serverName)
|
||||
|
||||
if (addedServers.length > 0) {
|
||||
// emit added servers
|
||||
const addPromises = addedServers.map(async (serverName) => {
|
||||
const addedServer = await this._safeSetupServer(mcpServers[serverName], serverName, userStateOfName[serverName]?.isOn)
|
||||
return {
|
||||
type: 'add',
|
||||
newServer: addedServer,
|
||||
name: serverName,
|
||||
} as const
|
||||
});
|
||||
const formattedAddedResponses = await Promise.all(addPromises);
|
||||
formattedAddedResponses.forEach((formattedResponse) => (this.mcpEmitters.serverEvent.onAdd.fire({ response: formattedResponse })));
|
||||
}
|
||||
|
||||
if (updatedServers.length > 0) {
|
||||
// emit updated servers
|
||||
const updatePromises = updatedServers.map(async (serverName) => {
|
||||
const prevServer = this.infoOfClientId[serverName]?.mcpServer;
|
||||
const newServer = await this._safeSetupServer(mcpServers[serverName], serverName, userStateOfName[serverName]?.isOn)
|
||||
return {
|
||||
type: 'update',
|
||||
prevServer,
|
||||
newServer: newServer,
|
||||
name: serverName,
|
||||
} as const
|
||||
});
|
||||
const formattedUpdatedResponses = await Promise.all(updatePromises);
|
||||
formattedUpdatedResponses.forEach((formattedResponse) => (this.mcpEmitters.serverEvent.onUpdate.fire({ response: formattedResponse })));
|
||||
}
|
||||
|
||||
if (deletedServers.length > 0) {
|
||||
// emit deleted servers
|
||||
const deletePromises = deletedServers.map(async (serverName) => {
|
||||
const prevServer = this.infoOfClientId[serverName]?.mcpServer;
|
||||
await this._closeClient(serverName)
|
||||
this._removeClient(serverName)
|
||||
return {
|
||||
type: 'delete',
|
||||
prevServer,
|
||||
name: serverName,
|
||||
} as const
|
||||
});
|
||||
const formattedDeletedResponses = await Promise.all(deletePromises);
|
||||
formattedDeletedResponses.forEach((formattedResponse) => (this.mcpEmitters.serverEvent.onDelete.fire({ response: formattedResponse })));
|
||||
}
|
||||
// close and delete the old client
|
||||
if (type === 'removed' || type === 'updated') {
|
||||
await this._closeClient(serverName)
|
||||
delete this.infoOfClientId[serverName]
|
||||
this.mcpEmitters.serverEvent.onDelete.fire({ response: { prevServer, name: serverName, } })
|
||||
}
|
||||
|
||||
// create a new client
|
||||
if (type === 'added' || type === 'updated') {
|
||||
const clientInfo = await this._createClient(mcpServersJSON[serverName], serverName, userStateOfName[serverName]?.isOn)
|
||||
this.infoOfClientId[serverName] = clientInfo
|
||||
this.mcpEmitters.serverEvent.onAdd.fire({ response: { newServer: clientInfo.mcpServer, name: serverName, } })
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
allChanges.forEach(({ serverName, type }) => {
|
||||
this._refreshingServerNames.delete(serverName)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private async _callSetupServer(server: MCPConfigFileEntryJSON, serverName: string, isOn = true) {
|
||||
private async _createClientUnsafe(server: MCPConfigFileEntryJSON, serverName: string, isOn: boolean): Promise<ClientInfo> {
|
||||
|
||||
const clientConfig = getClientConfig(serverName)
|
||||
const client = new Client(clientConfig)
|
||||
let transport: Transport;
|
||||
let formattedServer: MCPServer;
|
||||
let info: MCPServerNonError;
|
||||
|
||||
if (server.url) {
|
||||
// first try HTTP, fall back to SSE
|
||||
|
|
@ -228,7 +174,7 @@ export class MCPChannel implements IServerChannel {
|
|||
await client.connect(transport);
|
||||
console.log(`Connected via HTTP to ${serverName}`);
|
||||
const { tools } = await client.listTools()
|
||||
formattedServer = {
|
||||
info = {
|
||||
status: isOn ? 'success' : 'offline',
|
||||
tools: tools,
|
||||
command: server.url.toString(),
|
||||
|
|
@ -238,7 +184,7 @@ export class MCPChannel implements IServerChannel {
|
|||
transport = new SSEClientTransport(server.url);
|
||||
await client.connect(transport);
|
||||
console.log(`Connected via SSE to ${serverName}`);
|
||||
formattedServer = {
|
||||
info = {
|
||||
status: isOn ? 'success' : 'offline',
|
||||
tools: [],
|
||||
command: server.url.toString(),
|
||||
|
|
@ -264,7 +210,7 @@ export class MCPChannel implements IServerChannel {
|
|||
const fullCommand = `${server.command} ${server.args?.join(' ') || ''}`
|
||||
|
||||
// Format server object
|
||||
formattedServer = {
|
||||
info = {
|
||||
status: isOn ? 'success' : 'offline',
|
||||
tools: tools,
|
||||
command: fullCommand,
|
||||
|
|
@ -275,44 +221,25 @@ export class MCPChannel implements IServerChannel {
|
|||
}
|
||||
|
||||
|
||||
this.infoOfClientId[serverName] = { _client: client, mcpConfig: server, mcpServer: formattedServer }
|
||||
return formattedServer;
|
||||
return { _client: client, mcpServerEntryJSON: server, mcpServer: info }
|
||||
}
|
||||
|
||||
// Helper function to safely setup a server
|
||||
private async _safeSetupServer(serverConfig: MCPConfigFileEntryJSON, serverName: string, isOn = true) {
|
||||
private async _createClient(serverConfig: MCPConfigFileEntryJSON, serverName: string, isOn = true): Promise<ClientInfo> {
|
||||
try {
|
||||
return await this._callSetupServer(serverConfig, serverName, isOn)
|
||||
const c: ClientInfo = await this._createClientUnsafe(serverConfig, serverName, isOn)
|
||||
return c
|
||||
} catch (err) {
|
||||
const typedErr = err as Error
|
||||
console.error(`❌ Failed to connect to server "${serverName}":`, err)
|
||||
|
||||
let fullCommand = ''
|
||||
if (serverConfig.command) {
|
||||
fullCommand = `${serverConfig.command} ${serverConfig.args?.join(' ') || ''}`
|
||||
}
|
||||
|
||||
const formattedError: MCPServerError = {
|
||||
status: 'error',
|
||||
// tools: [],
|
||||
error: typedErr.message,
|
||||
command: fullCommand,
|
||||
}
|
||||
|
||||
// Add the error to the clients object
|
||||
this.infoOfClientId[serverName] = {
|
||||
mcpConfig: serverConfig,
|
||||
mcpServer: formattedError,
|
||||
}
|
||||
|
||||
return formattedError
|
||||
const fullCommand = !serverConfig.command ? '' : `${serverConfig.command} ${serverConfig.args?.join(' ') || ''}`
|
||||
const c: MCPServerError = { status: 'error', error: err + '', command: fullCommand, }
|
||||
return { mcpServerEntryJSON: serverConfig, mcpServer: c, }
|
||||
}
|
||||
}
|
||||
|
||||
private async _closeAllMCPServers() {
|
||||
for (const serverName in this.infoOfClientId) {
|
||||
await this._closeClient(serverName)
|
||||
this._removeClient(serverName)
|
||||
delete this.infoOfClientId[serverName]
|
||||
}
|
||||
console.log('Closed all MCP servers');
|
||||
}
|
||||
|
|
@ -324,46 +251,38 @@ export class MCPChannel implements IServerChannel {
|
|||
if (client) {
|
||||
await client.close()
|
||||
}
|
||||
// Remove the client from the clients object
|
||||
delete this.infoOfClientId[serverName]._client
|
||||
console.log(`Closed MCP server ${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.infoOfClientId[serverName]?.mcpServer
|
||||
// Handle turning on the server
|
||||
if (isOn) {
|
||||
// Handle turning on the server
|
||||
// this.mcpEmitters.serverEvent.onChangeLoading.fire(getLoadingServerObject(serverName, isOn))
|
||||
const formattedServer = await this._callSetupServer(this.infoOfClientId[serverName].mcpConfig, serverName)
|
||||
const clientInfo = await this._createClientUnsafe(this.infoOfClientId[serverName].mcpServerEntryJSON, serverName, isOn)
|
||||
this.mcpEmitters.serverEvent.onUpdate.fire({
|
||||
response: {
|
||||
type: 'update',
|
||||
name: serverName,
|
||||
newServer: formattedServer,
|
||||
newServer: clientInfo.mcpServer,
|
||||
prevServer: prevServer,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Handle turning off the server
|
||||
}
|
||||
// Handle turning off the server
|
||||
else {
|
||||
// this.mcpEmitters.serverEvent.onChangeLoading.fire(getLoadingServerObject(serverName, isOn))
|
||||
this._closeClient(serverName)
|
||||
delete this.infoOfClientId[serverName]._client
|
||||
|
||||
this.mcpEmitters.serverEvent.onUpdate.fire({
|
||||
response: {
|
||||
type: 'update',
|
||||
name: serverName,
|
||||
newServer: {
|
||||
status: 'offline',
|
||||
tools: [],
|
||||
command: '',
|
||||
// Explicitly set error to undefined
|
||||
// to reset the error state
|
||||
// Explicitly set error to undefined to reset the error state
|
||||
error: undefined,
|
||||
},
|
||||
prevServer: prevServer,
|
||||
|
|
|
|||
Loading…
Reference in a new issue