mirror of
https://github.com/hanzoai/code.git
synced 2026-09-30 20:53:35 +00:00
progress
This commit is contained in:
+10
-8
@@ -1,5 +1,5 @@
|
||||
import { VoidSwitch } from '../util/inputs.js';
|
||||
import { MCPConfigParseError, MCPServerEventParam, MCPServerObject, MCPServers } from '../../../../common/mcpServiceTypes.js';
|
||||
import { MCPConfigFileParseErrorResponse, MCPEventType, MCPEventResponse, MCPServerObject, MCPServerOfName } from '../../../../common/mcpServiceTypes.js';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useAccessor } from '../util/services.js';
|
||||
import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
|
||||
@@ -98,14 +98,14 @@ const MCPServersList = () => {
|
||||
|
||||
const accessor = useAccessor();
|
||||
const mcpService = accessor.get('IMCPService');
|
||||
const [mcpServers, setMCPServers] = useState<MCPServers>({});
|
||||
const [mcpServers, setMCPServers] = useState<MCPServerOfName>({});
|
||||
const [mcpConfigError, setMCPConfigError] = useState<string | null>(null);
|
||||
|
||||
// Get all servers from MCPConfigService
|
||||
useEffect(() => {
|
||||
console.log('RUNNING MCPServersList EFFECT');
|
||||
// Initial fetch
|
||||
const servers = mcpService.getMCPServers();
|
||||
const servers = mcpService.getMCPServerOfName();
|
||||
if (servers) {
|
||||
// Do something with the servers
|
||||
console.log('MCP Servers:', servers);
|
||||
@@ -114,10 +114,12 @@ const MCPServersList = () => {
|
||||
|
||||
// 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.onLoadingServers(handleListeners));
|
||||
disposables.push(mcpService.onConfigParsingError(handleListeners));
|
||||
|
||||
// Clean up subscription when component unmounts
|
||||
@@ -128,14 +130,14 @@ const MCPServersList = () => {
|
||||
|
||||
}, [mcpService]);
|
||||
|
||||
const handleListeners = (e: MCPServerEventParam | MCPConfigParseError) => {
|
||||
if (e.response.event === 'config-error') {
|
||||
const handleListeners = (e: MCPEventResponse | MCPConfigFileParseErrorResponse) => {
|
||||
if (e.response.type === 'config-file-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') {
|
||||
if (e.response.type === 'add' || e.response.type === 'update' || e.response.type === 'loading') {
|
||||
// Handle the add event
|
||||
const { name, newServer } = e.response;
|
||||
setMCPServers(prevServers => ({
|
||||
@@ -144,7 +146,7 @@ const MCPServersList = () => {
|
||||
}));
|
||||
return;
|
||||
}
|
||||
if (e.response.event === 'delete') {
|
||||
if (e.response.type === 'delete') {
|
||||
// Handle the delete event
|
||||
const { name, prevServer } = e.response;
|
||||
setMCPServers(prevServers => {
|
||||
|
||||
@@ -1249,7 +1249,7 @@ Alternatively, place a \`.voidrules\` file in the root of your workspace.
|
||||
<div className='mt-12 max-w-[600px]'>
|
||||
<div className='flex items-center justify-between mb-2'>
|
||||
<h2 className={`text-3xl`}>MCP</h2>
|
||||
<VoidButtonBgDarken className='px-4 py-1' onClick={async () => { await mcpService.openMCPConfigFile() }}>
|
||||
<VoidButtonBgDarken className='px-4 py-1' onClick={async () => { await mcpService.revealMCPConfigFile() }}>
|
||||
Add MCP Server
|
||||
</VoidButtonBgDarken>
|
||||
</div>
|
||||
|
||||
@@ -15,23 +15,27 @@ 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, MCPConfigParseError } from './mcpServiceTypes.js';
|
||||
import { MCPServerOfName, MCPConfigFileType, MCPConfigFileParseErrorResponse, MCPAddResponse, MCPUpdateResponse, MCPDeleteResponse, MCPEventResponse } from './mcpServiceTypes.js';
|
||||
import { Event, Emitter } from '../../../../base/common/event.js';
|
||||
import { InternalToolInfo } from './prompt/prompts.js';
|
||||
import { IVoidSettingsService } from './voidSettingsService.js';
|
||||
import { MCPServerStates } from './voidSettingsTypes.js';
|
||||
import { MCPServerStateOfName } from './voidSettingsTypes.js';
|
||||
|
||||
export interface IMCPService {
|
||||
readonly _serviceBrand: undefined;
|
||||
openMCPConfigFile(): Promise<void>;
|
||||
getMCPServers(): MCPServers;
|
||||
getAllToolsFormatted(): InternalToolInfo[];
|
||||
revealMCPConfigFile(): Promise<void>;
|
||||
getMCPServerOfName(): MCPServerOfName;
|
||||
|
||||
getCurrentTools(): InternalToolInfo[];
|
||||
|
||||
toggleServer(serverName: string, isOn: boolean): Promise<void>;
|
||||
onDidAddServer: Event<MCPServerEventAddParam>;
|
||||
onDidUpdateServer: Event<MCPServerEventUpdateParam>;
|
||||
onDidDeleteServer: Event<MCPServerEventDeleteParam>;
|
||||
onLoadingServers: Event<MCPServerEventLoadingParam>;
|
||||
onConfigParsingError: Event<MCPConfigParseError>;
|
||||
|
||||
onDidAddServer: Event<MCPAddResponse>;
|
||||
onDidUpdateServer: Event<MCPUpdateResponse>;
|
||||
onDidDeleteServer: Event<MCPDeleteResponse>;
|
||||
|
||||
// onLoadingServers: Event<MCPServerEventLoadingParam>;
|
||||
onConfigParsingError: Event<MCPConfigFileParseErrorResponse>;
|
||||
}
|
||||
|
||||
export const IMCPService = createDecorator<IMCPService>('mcpConfigService');
|
||||
@@ -48,18 +52,20 @@ class MCPService extends Disposable implements IMCPService {
|
||||
private readonly channel: IChannel // MCPChannel
|
||||
|
||||
// list of MCP servers pulled from mcpChannel
|
||||
private mcpServers: MCPServers = {}
|
||||
private readonly mcpServers: MCPServerOfName = {}
|
||||
|
||||
// Emitters for server events
|
||||
private readonly _onDidAddServer = new Emitter<MCPServerEventAddParam>();
|
||||
private readonly _onDidUpdateServer = new Emitter<MCPServerEventUpdateParam>();
|
||||
private readonly _onDidDeleteServer = new Emitter<MCPServerEventDeleteParam>();
|
||||
private readonly _onLoadingServers = new Emitter<MCPServerEventLoadingParam>();
|
||||
private readonly _onConfigParsingError = new Emitter<MCPConfigParseError>();
|
||||
private readonly _onDidAddServer = new Emitter<MCPAddResponse>();
|
||||
private readonly _onDidUpdateServer = new Emitter<MCPUpdateResponse>();
|
||||
private readonly _onDidDeleteServer = new Emitter<MCPDeleteResponse>();
|
||||
public readonly onDidAddServer = this._onDidAddServer.event;
|
||||
public readonly onDidUpdateServer = this._onDidUpdateServer.event;
|
||||
public readonly onDidDeleteServer = this._onDidDeleteServer.event;
|
||||
public readonly onLoadingServers = this._onLoadingServers.event;
|
||||
|
||||
// private readonly _onLoadingServers = new Emitter<MCPServerEventLoadingParam>();
|
||||
// public readonly onLoadingServers = this._onLoadingServers.event;
|
||||
|
||||
private readonly _onConfigParsingError = new Emitter<MCPConfigFileParseErrorResponse>();
|
||||
public readonly onConfigParsingError = this._onConfigParsingError.event;
|
||||
|
||||
constructor(
|
||||
@@ -74,10 +80,11 @@ class MCPService extends Disposable implements IMCPService {
|
||||
// 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<MCPServerEventAddParam>)(e => this._onServerEvent(e)));
|
||||
this._register((this.channel.listen('onUpdate_server') satisfies Event<MCPServerEventUpdateParam>)(e => this._onServerEvent(e)));
|
||||
this._register((this.channel.listen('onDelete_server') satisfies Event<MCPServerEventDeleteParam>)(e => this._onServerEvent(e)));
|
||||
this._register((this.channel.listen('onLoading_server') satisfies Event<MCPServerEventLoadingParam>)(e => this._onServerEvent(e)));
|
||||
this._register((this.channel.listen('onAdd_server') satisfies Event<MCPAddResponse>)(e => this._onServerEvent(e)));
|
||||
this._register((this.channel.listen('onUpdate_server') satisfies Event<MCPUpdateResponse>)(e => this._onServerEvent(e)));
|
||||
this._register((this.channel.listen('onDelete_server') satisfies Event<MCPDeleteResponse>)(e => this._onServerEvent(e)));
|
||||
|
||||
// this._register((this.channel.listen('onLoading_server') satisfies Event<MCPServerEventLoadingParam>)(e => this._onServerEvent(e)));
|
||||
// Initialize the service
|
||||
this._initialize();
|
||||
}
|
||||
@@ -104,12 +111,11 @@ class MCPService extends Disposable implements IMCPService {
|
||||
private async _initialize() {
|
||||
try {
|
||||
// Get the MCP config file path
|
||||
const mcpConfigUri = await this._getMCPConfigPath();
|
||||
const mcpConfigUri = await this._getMCPConfigFilePath();
|
||||
|
||||
// Check if the file exists
|
||||
// create file if it doesn't exist
|
||||
const fileExists = await this._configFileExists(mcpConfigUri);
|
||||
if (!fileExists) {
|
||||
// Create the file if it doesn't exist
|
||||
await this._createMCPConfigFile(mcpConfigUri);
|
||||
console.log('MCP Config file created:', mcpConfigUri.toString());
|
||||
}
|
||||
@@ -117,15 +123,16 @@ class MCPService extends Disposable implements IMCPService {
|
||||
// Wait for VoidSettingsService to initialize before proceeding
|
||||
await this.voidSettingsService.waitForInitState;
|
||||
|
||||
// Parse the MCP config file
|
||||
// read MCP config file
|
||||
const mcpConfig = await this._parseMCPConfigFile();
|
||||
if (!mcpConfig) throw new Error(`MCP config file not found`);
|
||||
if (!mcpConfig.mcpServers) throw new Error(`MCP config file did not have an 'mcpServers' field`);
|
||||
|
||||
if (mcpConfig && mcpConfig.mcpServers) {
|
||||
const updatedServerStates = await this._handleServerStateChange(mcpConfig);
|
||||
// update state based on config file
|
||||
const updatedServerStates = await this._handleServerStateChange(mcpConfig);
|
||||
|
||||
// Setup the server list
|
||||
this.channel.call('setupServers', { mcpConfig, serverStates: updatedServerStates })
|
||||
}
|
||||
// Setup the server list
|
||||
this.channel.call('setupServers', { mcpConfig, serverStates: updatedServerStates })
|
||||
|
||||
|
||||
// Add a watcher to the MCP config file
|
||||
@@ -136,59 +143,48 @@ class MCPService extends Disposable implements IMCPService {
|
||||
}
|
||||
}
|
||||
|
||||
private async _onServerEvent(e: MCPServerEventParam) {
|
||||
|
||||
if (e.response.event === 'add') {
|
||||
// Add to the mcpServers list
|
||||
this.mcpServers[e.response.name] = e.response.newServer;
|
||||
// Fire the event to notify browser
|
||||
this._onDidAddServer.fire(e as MCPServerEventAddParam);
|
||||
private async _onServerEvent(e: MCPEventResponse) {
|
||||
const r = e.response
|
||||
if (r.type === 'add') {
|
||||
this.mcpServers[r.name] = r.newServer;
|
||||
this._onDidAddServer.fire(e as MCPAddResponse);
|
||||
}
|
||||
|
||||
if (e.response.event === 'update') {
|
||||
// Update the mcpServers list
|
||||
this.mcpServers[e.response.name] = e.response.newServer;
|
||||
// Fire the event to notify browser
|
||||
this._onDidUpdateServer.fire(e as MCPServerEventUpdateParam);
|
||||
if (r.type === 'update') {
|
||||
this.mcpServers[r.name] = r.newServer;
|
||||
this._onDidUpdateServer.fire(e as MCPUpdateResponse);
|
||||
}
|
||||
|
||||
if (e.response.event === 'delete') {
|
||||
// Remove from the mcpServers list
|
||||
delete this.mcpServers[e.response.name];
|
||||
// Fire the event to notify browser
|
||||
this._onDidDeleteServer.fire(e as MCPServerEventDeleteParam);
|
||||
}
|
||||
|
||||
if (e.response.event === 'loading') {
|
||||
// Update the mcpServers list
|
||||
this.mcpServers[e.response.name] = e.response.newServer;
|
||||
// Fire the event to notify browser
|
||||
this._onLoadingServers.fire(e as MCPServerEventLoadingParam);
|
||||
if (r.type === 'delete') {
|
||||
delete this.mcpServers[r.name];
|
||||
this._onDidDeleteServer.fire(e as MCPDeleteResponse);
|
||||
}
|
||||
// if (e.response.event === 'loading') {
|
||||
// // Update the mcpServers list
|
||||
// this.mcpServers[e.response.name] = e.response.newServer;
|
||||
// // Fire the event to notify browser
|
||||
// this._onLoadingServers.fire(e);
|
||||
// }
|
||||
}
|
||||
|
||||
// Create the file/directory if it doesn't exist
|
||||
private async _createMCPConfigFile(mcpConfigUri: URI): Promise<void> {
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
await this.fileService.createFile(mcpConfigUri.with({ path: mcpConfigUri.path }));
|
||||
|
||||
// Create the MCP config file with default content
|
||||
const buffer = VSBuffer.fromString(this.MCP_CONFIG_SAMPLE_STRING);
|
||||
await this.fileService.writeFile(mcpConfigUri, buffer);
|
||||
}
|
||||
|
||||
private async _parseMCPConfigFile(): Promise<MCPConfig | null> {
|
||||
// Remove any previous config parsing error
|
||||
// This isn't super intuitive, but it works
|
||||
// Remove any previous config parsing error
|
||||
// This isn't super intuitive, but it works
|
||||
private async _parseMCPConfigFile(): Promise<MCPConfigFileType | null> {
|
||||
// clear error
|
||||
this._onConfigParsingError.fire({
|
||||
response: {
|
||||
event: 'config-error',
|
||||
type: 'config-file-error',
|
||||
error: null
|
||||
}
|
||||
});
|
||||
|
||||
// Process config file
|
||||
const mcpConfigUri = await this._getMCPConfigPath();
|
||||
const mcpConfigUri = await this._getMCPConfigFilePath();
|
||||
|
||||
try {
|
||||
const fileContent = await this.fileService.readFile(mcpConfigUri);
|
||||
@@ -197,13 +193,13 @@ class MCPService extends Disposable implements IMCPService {
|
||||
if (!configJson.mcpServers) {
|
||||
throw new Error('Invalid MCP config file: missing mcpServers property');
|
||||
}
|
||||
return configJson as MCPConfig;
|
||||
return configJson as MCPConfigFileType;
|
||||
} catch (error) {
|
||||
const fullError = `Error parsing MCP config file: ${error}`;
|
||||
console.error(fullError);
|
||||
this._onConfigParsingError.fire({
|
||||
response: {
|
||||
event: 'config-error',
|
||||
type: 'config-file-error',
|
||||
error: fullError
|
||||
}
|
||||
});
|
||||
@@ -212,7 +208,7 @@ class MCPService extends Disposable implements IMCPService {
|
||||
}
|
||||
|
||||
private async _setMCPConfigFileWatch(): Promise<void> {
|
||||
const mcpConfigUri = await this._getMCPConfigPath();
|
||||
const mcpConfigUri = await this._getMCPConfigFilePath();
|
||||
|
||||
// Watch the file for changes
|
||||
this.mcpFileWatcher = this.fileService.watch(mcpConfigUri);
|
||||
@@ -241,10 +237,10 @@ class MCPService extends Disposable implements IMCPService {
|
||||
|
||||
// Client-side functions
|
||||
|
||||
public async openMCPConfigFile(): Promise<void> {
|
||||
public async revealMCPConfigFile(): Promise<void> {
|
||||
try {
|
||||
// Get the MCP config file path
|
||||
const mcpConfigUri = await this._getMCPConfigPath();
|
||||
const mcpConfigUri = await this._getMCPConfigFilePath();
|
||||
|
||||
// Open the MCP config file in the editor
|
||||
await this.editorService.openEditor({
|
||||
@@ -260,12 +256,12 @@ class MCPService extends Disposable implements IMCPService {
|
||||
}
|
||||
}
|
||||
|
||||
public getMCPServers(): MCPServers {
|
||||
public getMCPServerOfName(): MCPServerOfName {
|
||||
// Call the getMCPServers method in the main process
|
||||
return this.mcpServers;
|
||||
}
|
||||
|
||||
public getAllToolsFormatted(): InternalToolInfo[] {
|
||||
public getCurrentTools(): InternalToolInfo[] {
|
||||
const allTools = Object.values(this.mcpServers).flatMap(server => {
|
||||
return server.tools.map(tool => {
|
||||
// Convert JsonSchema to the expected format
|
||||
@@ -293,15 +289,14 @@ class MCPService extends Disposable implements IMCPService {
|
||||
public async toggleServer(serverName: string, isOn: boolean): Promise<void> {
|
||||
this.channel.call('toggleServer', { serverName, isOn })
|
||||
// Update the server state in the local mcpServers list
|
||||
await this.voidSettingsService.updateMCPServerState(serverName, isOn);
|
||||
await this.voidSettingsService.setMCPServerState(serverName, isOn);
|
||||
}
|
||||
|
||||
// utility functions
|
||||
|
||||
private async _getMCPConfigPath(): Promise<URI> {
|
||||
private async _getMCPConfigFilePath(): Promise<URI> {
|
||||
// Get the appropriate directory based on dev mode
|
||||
const appName = this.productService.dataFolderName
|
||||
|
||||
const userHome = await this.pathService.userHome();
|
||||
const mcpConfigPath = join(userHome.path, appName, this.MCP_CONFIG_FILE_NAME);
|
||||
return URI.file(mcpConfigPath);
|
||||
@@ -319,9 +314,9 @@ class MCPService extends Disposable implements IMCPService {
|
||||
}
|
||||
|
||||
// Handle server state changes
|
||||
private async _handleServerStateChange(mcpConfig: MCPConfig): Promise<MCPServerStates> {
|
||||
private async _handleServerStateChange(mcpConfig: MCPConfigFileType): Promise<MCPServerStateOfName> {
|
||||
// Get the server states from Void Settings Service
|
||||
const savedServerStates = this.voidSettingsService.state.mcpServerStates;
|
||||
const savedServerStates = this.voidSettingsService.state.mcpServerStateOfName;
|
||||
|
||||
// Parse the MCP config file for servers
|
||||
const availableServers = Object.keys(mcpConfig.mcpServers);
|
||||
@@ -331,7 +326,7 @@ class MCPService extends Disposable implements IMCPService {
|
||||
const addedServersObject = addedServers.reduce((acc, serverName) => {
|
||||
acc[serverName] = { isOn: true };
|
||||
return acc;
|
||||
}, {} as MCPServerStates);
|
||||
}, {} as MCPServerStateOfName);
|
||||
await this.voidSettingsService.addMCPServers(addedServersObject);
|
||||
|
||||
// Handle removed servers
|
||||
@@ -344,7 +339,7 @@ class MCPService extends Disposable implements IMCPService {
|
||||
acc[serverName] = savedServerStates[serverName];
|
||||
}
|
||||
return acc;
|
||||
}, {} as MCPServerStates);
|
||||
}, {} as MCPServerStateOfName);
|
||||
|
||||
return updatedServers;
|
||||
}
|
||||
|
||||
@@ -23,103 +23,100 @@
|
||||
/* Core JSON‑RPC envelope */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
export interface JsonRpcSuccess<T> {
|
||||
/** JSON‑RPC version – always '2.0' */
|
||||
jsonrpc: '2.0';
|
||||
/** Request identifier echoed back by the server */
|
||||
id: string | number | null;
|
||||
/** The successful result payload */
|
||||
result: T;
|
||||
}
|
||||
// export interface JsonRpcSuccess<T> {
|
||||
// /** JSON‑RPC version – always '2.0' */
|
||||
// jsonrpc: '2.0';
|
||||
// /** Request identifier echoed back by the server */
|
||||
// id: string | number | null;
|
||||
// /** The successful result payload */
|
||||
// result: T;
|
||||
// }
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* Utility: pagination */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
export interface Paginated {
|
||||
/** Opaque cursor for fetching the next page */
|
||||
nextCursor?: string;
|
||||
}
|
||||
// export interface Paginated {
|
||||
// /** Opaque cursor for fetching the next page */
|
||||
// nextCursor?: string;
|
||||
// }
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* 1. tools/list */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
/** Minimal JSON‑Schema placeholder – adapt if you need stricter typing */
|
||||
export type JsonSchema = Record<string, unknown>;
|
||||
|
||||
export interface Tool {
|
||||
export interface MCPTool {
|
||||
/** Unique tool identifier */
|
||||
name: string;
|
||||
/** Human‑readable description */
|
||||
description?: string;
|
||||
/** JSON schema describing expected arguments */
|
||||
inputSchema?: JsonSchema;
|
||||
inputSchema?: Record<string, unknown>;
|
||||
/** Free‑form annotations describing behaviour, security, etc. */
|
||||
annotations?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface ToolsListResult extends Paginated {
|
||||
tools: Tool[];
|
||||
}
|
||||
// export interface ToolsListResult extends Paginated {
|
||||
// tools: MCPTool[];
|
||||
// }
|
||||
|
||||
export type ToolsListResponse = JsonRpcSuccess<ToolsListResult>;
|
||||
// export type ToolsListResponse = JsonRpcSuccess<ToolsListResult>;
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* 2. prompts/list */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
export interface PromptArgument {
|
||||
name: string;
|
||||
description?: string;
|
||||
/** Whether the argument is required */
|
||||
required?: boolean;
|
||||
}
|
||||
// export interface PromptArgument {
|
||||
// name: string;
|
||||
// description?: string;
|
||||
// /** Whether the argument is required */
|
||||
// required?: boolean;
|
||||
// }
|
||||
|
||||
export interface Prompt {
|
||||
name: string;
|
||||
description?: string;
|
||||
arguments?: PromptArgument[];
|
||||
}
|
||||
// export interface Prompt {
|
||||
// name: string;
|
||||
// description?: string;
|
||||
// arguments?: PromptArgument[];
|
||||
// }
|
||||
|
||||
export interface PromptsListResult extends Paginated {
|
||||
prompts: Prompt[];
|
||||
}
|
||||
// export interface PromptsListResult extends Paginated {
|
||||
// prompts: Prompt[];
|
||||
// }
|
||||
|
||||
export type PromptsListResponse = JsonRpcSuccess<PromptsListResult>;
|
||||
// export type PromptsListResponse = JsonRpcSuccess<PromptsListResult>;
|
||||
|
||||
/* -------------------------------------------------- */
|
||||
/* 3. tools/call */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
/** Additional resource structure that can be embedded in tool results */
|
||||
export interface Resource {
|
||||
uri: string;
|
||||
mimeType: string;
|
||||
/** Either plain‑text or base64‑encoded binary data */
|
||||
text?: string;
|
||||
data?: string;
|
||||
}
|
||||
// export interface Resource {
|
||||
// uri: string;
|
||||
// mimeType: string;
|
||||
// /** Either plain‑text or base64‑encoded binary data */
|
||||
// text?: string;
|
||||
// data?: string;
|
||||
// }
|
||||
|
||||
/** Individual content items returned by a tool */
|
||||
export type ToolContent =
|
||||
| { type: 'text'; text: string }
|
||||
| { type: 'image'; data: string; mimeType: string }
|
||||
| { type: 'audio'; data: string; mimeType: string }
|
||||
| { type: 'resource'; resource: Resource };
|
||||
// export type ToolContent =
|
||||
// | { type: 'text'; text: string }
|
||||
// | { type: 'image'; data: string; mimeType: string }
|
||||
// | { type: 'audio'; data: string; mimeType: string }
|
||||
// | { type: 'resource'; resource: Resource };
|
||||
|
||||
export interface ToolCallResult {
|
||||
/** List of content parts (text, images, resources, etc.) */
|
||||
content: ToolContent[];
|
||||
/** True if the tool itself encountered a domain‑level error */
|
||||
isError?: boolean;
|
||||
}
|
||||
// export interface ToolCallResult {
|
||||
// /** List of content parts (text, images, resources, etc.) */
|
||||
// content: ToolContent[];
|
||||
// /** True if the tool itself encountered a domain‑level error */
|
||||
// isError?: boolean;
|
||||
// }
|
||||
|
||||
export type ToolCallResponse = JsonRpcSuccess<ToolCallResult>;
|
||||
// export type ToolCallResponse = JsonRpcSuccess<ToolCallResult>;
|
||||
|
||||
// MCP SERVER CONFIG FILE TYPES -----------------------------
|
||||
|
||||
export interface MCPServerConfig {
|
||||
export interface MCPConfigFileServerType {
|
||||
// Command-based server properties
|
||||
command?: string;
|
||||
args?: string[];
|
||||
@@ -130,30 +127,23 @@ export interface MCPServerConfig {
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface MCPConfig {
|
||||
mcpServers: Record<string, MCPServerConfig>;
|
||||
export interface MCPConfigFileType {
|
||||
mcpServers: Record<string, MCPConfigFileServerType>;
|
||||
}
|
||||
|
||||
export interface MCPConfigParseError {
|
||||
// Error message
|
||||
response: {
|
||||
event: 'config-error';
|
||||
error: string | null;
|
||||
}
|
||||
}
|
||||
|
||||
// SERVER EVENT TYPES ------------------------------------------
|
||||
|
||||
export interface MCPServerObject {
|
||||
// Command-based server properties
|
||||
tools: Tool[],
|
||||
tools: MCPTool[],
|
||||
status: 'loading' | 'error' | 'success' | 'offline',
|
||||
isOn: boolean,
|
||||
command?: string,
|
||||
error?: string,
|
||||
}
|
||||
|
||||
export interface MCPServers {
|
||||
export interface MCPServerOfName {
|
||||
[serverName: string]: MCPServerObject;
|
||||
}
|
||||
|
||||
@@ -162,63 +152,67 @@ export type MCPServerSuccessModel = MCPServerObject;
|
||||
export type MCPServerErrorModel = Omit<MCPServerObject, 'error'> & { error: string };
|
||||
|
||||
|
||||
export type MCPServerSetupParams<serverResponse> = {
|
||||
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<serverResponse> = Parameters<MCPServerSetupParams<serverResponse>['onSuccess']>[0]
|
||||
export type EventMCPServerSetupOnError<serverResponse> = Parameters<MCPServerSetupParams<serverResponse>['onError']>[0]
|
||||
|
||||
type MCPServerEventType = 'add' | 'update' | 'delete' | 'loading';
|
||||
export type EventMCPServerSetupOnSuccess = Parameters<MCPServerSetupParams['onSuccess']>[0]
|
||||
export type EventMCPServerSetupOnError = Parameters<MCPServerSetupParams['onError']>[0]
|
||||
|
||||
export type MCPServerModel = MCPServerSuccessModel | MCPServerErrorModel;
|
||||
|
||||
interface MCPServerResponseBase {
|
||||
|
||||
export type MCPEventType = {
|
||||
type: 'add';
|
||||
name: string;
|
||||
event: MCPServerEventType;
|
||||
newServer?: MCPServerModel;
|
||||
prevServer?: MCPServerModel;
|
||||
prevServer?: undefined;
|
||||
newServer: MCPServerModel;
|
||||
} | {
|
||||
type: 'update';
|
||||
name: string;
|
||||
prevServer: MCPServerModel;
|
||||
newServer: MCPServerModel;
|
||||
} | {
|
||||
type: 'delete';
|
||||
name: string;
|
||||
newServer?: undefined;
|
||||
prevServer: MCPServerModel;
|
||||
} | {
|
||||
type: 'loading';
|
||||
name: string;
|
||||
prevServer?: undefined;
|
||||
newServer: MCPServerModel;
|
||||
}
|
||||
|
||||
type EventTypeConstraints = {
|
||||
'add': {
|
||||
prevServer?: never;
|
||||
newServer: MCPServerModel;
|
||||
};
|
||||
'update': {
|
||||
prevServer: MCPServerModel;
|
||||
newServer: MCPServerModel;
|
||||
};
|
||||
'delete': {
|
||||
newServer?: never;
|
||||
prevServer: MCPServerModel;
|
||||
};
|
||||
'loading': {
|
||||
prevServer?: never;
|
||||
newServer: MCPServerModel;
|
||||
// Response types
|
||||
export type MCPAddResponse = { response: MCPEventType & { type: 'add' } }
|
||||
export type MCPUpdateResponse = { response: MCPEventType & { type: 'update' } }
|
||||
export type MCPDeleteResponse = { response: MCPEventType & { type: 'delete' } }
|
||||
// export type MCPLoadingResponse = { response: MCPEventType & { type: 'loading' } }
|
||||
|
||||
|
||||
export type MCPEventResponse = { response: MCPEventType }
|
||||
|
||||
export interface MCPConfigFileParseErrorResponse {
|
||||
response: {
|
||||
type: 'config-file-error';
|
||||
error: string | null;
|
||||
}
|
||||
}
|
||||
|
||||
type MCPEventResponse<T extends MCPServerEventType> = Omit<MCPServerResponseBase, 'event' | keyof EventTypeConstraints> & EventTypeConstraints[T] & { event: T };
|
||||
|
||||
// Response types
|
||||
export type MCPAddResponse = MCPEventResponse<'add'>;
|
||||
export type MCPUpdateResponse = MCPEventResponse<'update'>;
|
||||
export type MCPDeleteResponse = MCPEventResponse<'delete'>;
|
||||
export type MCPLoadingResponse = MCPEventResponse<'loading'>;
|
||||
|
||||
export type MCPServerResponse = MCPAddResponse | MCPUpdateResponse | MCPDeleteResponse | MCPLoadingResponse;
|
||||
// export type MCPServerResponse = MCPAddResponse | MCPUpdateResponse | MCPDeleteResponse | MCPLoadingResponse;
|
||||
|
||||
// Event parameter types
|
||||
export type MCPServerEventAddParam = { response: MCPAddResponse };
|
||||
export type MCPServerEventUpdateParam = { response: MCPUpdateResponse };
|
||||
export type MCPServerEventDeleteParam = { response: MCPDeleteResponse };
|
||||
export type MCPServerEventLoadingParam = { response: MCPLoadingResponse };
|
||||
// export type MCPServerEventAddParam = { response: MCPAddResponse };
|
||||
// export type MCPServerEventUpdateParam = { response: MCPUpdateResponse };
|
||||
// export type MCPServerEventDeleteParam = { response: MCPDeleteResponse };
|
||||
// export type MCPServerEventLoadingParam = { response: MCPLoadingResponse };
|
||||
|
||||
// Event Param union type
|
||||
export type MCPServerEventParam = MCPServerEventAddParam | MCPServerEventUpdateParam | MCPServerEventDeleteParam | MCPServerEventLoadingParam;
|
||||
// export type MCPServerEventParam = MCPServerEventAddParam | MCPServerEventUpdateParam | MCPServerEventDeleteParam | MCPServerEventLoadingParam;
|
||||
|
||||
|
||||
|
||||
@@ -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, MCPServerStates } from './voidSettingsTypes.js';
|
||||
import { defaultSettingsOfProvider, FeatureName, ProviderName, ModelSelectionOfFeature, SettingsOfProvider, SettingName, providerNames, ModelSelection, modelSelectionsEqual, featureNames, VoidStatefulModelInfo, GlobalSettings, GlobalSettingName, defaultGlobalSettings, ModelSelectionOptions, OptionsOfModelSelection, ChatMode, OverridesOfModel, defaultOverridesOfModel, MCPServerStateOfName } 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 mcpServerStates: MCPServerStates;
|
||||
readonly mcpServerStateOfName: MCPServerStateOfName;
|
||||
|
||||
readonly _modelOptions: ModelOption[] // computed based on the two above items
|
||||
}
|
||||
@@ -63,7 +63,7 @@ export interface IVoidSettingsService {
|
||||
setModelSelectionOfFeature: SetModelSelectionOfFeatureFn;
|
||||
setOptionsOfModelSelection: SetOptionsOfModelSelection;
|
||||
setGlobalSetting: SetGlobalSettingFn;
|
||||
setMCPServerStates: (newStates: MCPServerStates) => Promise<void>;
|
||||
// setMCPServerStates: (newStates: MCPServerStates) => Promise<void>;
|
||||
|
||||
// setting to undefined CLEARS it, unlike others:
|
||||
setOverridesOfModel(providerName: ProviderName, modelName: string, overrides: Partial<ModelOverrides> | undefined): Promise<void>;
|
||||
@@ -75,9 +75,10 @@ export interface IVoidSettingsService {
|
||||
toggleModelHidden(providerName: ProviderName, modelName: string): void;
|
||||
addModel(providerName: ProviderName, modelName: string): void;
|
||||
deleteModel(providerName: ProviderName, modelName: string): boolean;
|
||||
addMCPServers(newMCPStates: MCPServerStates): Promise<void>;
|
||||
|
||||
addMCPServers(newMCPStates: MCPServerStateOfName): Promise<void>;
|
||||
removeMCPServers(serverNames: string[]): Promise<void>;
|
||||
updateMCPServerState(serverName: string, newIsOn: boolean): Promise<void>;
|
||||
setMCPServerState(serverName: string, newIsOn: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -217,7 +218,7 @@ const defaultState = () => {
|
||||
optionsOfModelSelection: { 'Chat': {}, 'Ctrl+K': {}, 'Autocomplete': {}, 'Apply': {} },
|
||||
overridesOfModel: deepClone(defaultOverridesOfModel),
|
||||
_modelOptions: [], // computed later
|
||||
mcpServerStates: {},
|
||||
mcpServerStateOfName: {},
|
||||
}
|
||||
return d
|
||||
}
|
||||
@@ -367,7 +368,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
|
||||
|
||||
const newGlobalSettings = this.state.globalSettings
|
||||
const newOverridesOfModel = this.state.overridesOfModel
|
||||
const newMCPServerStates = this.state.mcpServerStates
|
||||
const newMCPServerStateOfName = this.state.mcpServerStateOfName
|
||||
|
||||
const newState = {
|
||||
modelSelectionOfFeature: newModelSelectionOfFeature,
|
||||
@@ -375,7 +376,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
|
||||
settingsOfProvider: newSettingsOfProvider,
|
||||
globalSettings: newGlobalSettings,
|
||||
overridesOfModel: newOverridesOfModel,
|
||||
mcpServerStates: newMCPServerStates,
|
||||
mcpServerStateOfName: newMCPServerStateOfName,
|
||||
}
|
||||
|
||||
this.state = _validatedModelState(newState)
|
||||
@@ -494,21 +495,6 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
|
||||
this._metricsService.capture('Autodetect Models', { providerName, newModels: newModels, ...logging })
|
||||
}
|
||||
}
|
||||
|
||||
setMCPServerStates = async (newStates: MCPServerStates) => {
|
||||
const newState: VoidSettingsState = {
|
||||
...this.state,
|
||||
mcpServerStates: {
|
||||
...this.state.mcpServerStates,
|
||||
...newStates
|
||||
}
|
||||
};
|
||||
this.state = _validatedModelState(newState);
|
||||
await this._storeState();
|
||||
// this._onDidChangeState.fire();
|
||||
this._metricsService.capture('Set MCP Server States', { newStates });
|
||||
}
|
||||
|
||||
toggleModelHidden(providerName: ProviderName, modelName: string) {
|
||||
|
||||
|
||||
@@ -555,19 +541,32 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
|
||||
}
|
||||
|
||||
// MCP Server State
|
||||
private _setMCPServerStates = async (newStates: MCPServerStateOfName) => {
|
||||
const newState: VoidSettingsState = {
|
||||
...this.state,
|
||||
mcpServerStateOfName: {
|
||||
...this.state.mcpServerStateOfName,
|
||||
...newStates
|
||||
}
|
||||
};
|
||||
this.state = _validatedModelState(newState);
|
||||
await this._storeState();
|
||||
this._onDidChangeState.fire();
|
||||
this._metricsService.capture('Set MCP Server States', { newStates });
|
||||
}
|
||||
|
||||
addMCPServers = async (newMCPStates: MCPServerStates) => {
|
||||
const { mcpServerStates } = this.state
|
||||
addMCPServers = async (newMCPStates: MCPServerStateOfName) => {
|
||||
const { mcpServerStateOfName: mcpServerStates } = this.state
|
||||
const newMCPServerStates = {
|
||||
...mcpServerStates,
|
||||
...newMCPStates,
|
||||
}
|
||||
await this.setMCPServerStates(newMCPServerStates)
|
||||
await this._setMCPServerStates(newMCPServerStates)
|
||||
this._metricsService.capture('Add MCP Server', { servers: Object.keys(newMCPStates).join(', ') });
|
||||
}
|
||||
|
||||
removeMCPServers = async (serverNames: string[]) => {
|
||||
const { mcpServerStates } = this.state
|
||||
const { mcpServerStateOfName: mcpServerStates } = this.state
|
||||
const newMCPServerStates = {
|
||||
...mcpServerStates,
|
||||
}
|
||||
@@ -576,12 +575,12 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
|
||||
delete newMCPServerStates[serverName]
|
||||
}
|
||||
})
|
||||
await this.setMCPServerStates(newMCPServerStates)
|
||||
await this._setMCPServerStates(newMCPServerStates)
|
||||
this._metricsService.capture('Remove MCP Server', { servers: serverNames.join(', ') });
|
||||
}
|
||||
|
||||
updateMCPServerState = async (serverName: string, newIsOn: boolean) => {
|
||||
const { mcpServerStates } = this.state
|
||||
setMCPServerState = async (serverName: string, newIsOn: boolean) => {
|
||||
const { mcpServerStateOfName: mcpServerStates } = this.state
|
||||
if (!(serverName in mcpServerStates)) return // if not in list, do nothing
|
||||
const newMCPServerStates = {
|
||||
...mcpServerStates,
|
||||
@@ -589,7 +588,7 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
|
||||
isOn: newIsOn,
|
||||
},
|
||||
}
|
||||
await this.setMCPServerStates(newMCPServerStates)
|
||||
await this._setMCPServerStates(newMCPServerStates)
|
||||
this._metricsService.capture('Update MCP Server State', { serverName, newIsOn });
|
||||
}
|
||||
|
||||
|
||||
@@ -494,7 +494,7 @@ export const defaultOverridesOfModel = overridesOfModel
|
||||
|
||||
|
||||
|
||||
export interface MCPServerStates {
|
||||
export interface MCPServerStateOfName {
|
||||
[serverName: string]: MCPServerState;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,41 +13,60 @@ 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 { MCPConfig, MCPServerConfig, MCPServerErrorModel, MCPAddResponse, MCPServerEventAddParam, MCPServerEventUpdateParam, MCPServerEventDeleteParam, MCPUpdateResponse, MCPServerModel, MCPDeleteResponse, MCPServerEventLoadingParam } from '../common/mcpServiceTypes.js';
|
||||
import { MCPConfigFileType, MCPConfigFileServerType, MCPServerErrorModel, MCPServerModel, MCPAddResponse, MCPUpdateResponse, MCPDeleteResponse } from '../common/mcpServiceTypes.js';
|
||||
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
||||
import { equals } from '../../../../base/common/objects.js';
|
||||
import { MCPServerStates } from '../common/voidSettingsTypes.js';
|
||||
import { MCPServerStateOfName } 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`,
|
||||
version: '0.1.0',
|
||||
// debug: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class MCPChannel implements IServerChannel {
|
||||
|
||||
// connected clients
|
||||
private clients: { [clientId: string]: { client?: Client, mcpConfig: MCPServerConfig, formattedServer: MCPServerModel } } = {}
|
||||
private getClientConfig(serverName: string) {
|
||||
return {
|
||||
name: `${serverName}-client`,
|
||||
version: '0.1.0',
|
||||
// debug: true,
|
||||
}
|
||||
}
|
||||
private clients: { [clientId: string]: { client?: Client, mcpConfig: MCPConfigFileServerType, formattedServer: MCPServerModel } } = {}
|
||||
|
||||
// mcp emitters
|
||||
private readonly mcpEmitters = {
|
||||
serverEvent: {
|
||||
add: new Emitter<MCPServerEventAddParam>(),
|
||||
update: new Emitter<MCPServerEventUpdateParam>(),
|
||||
delete: new Emitter<MCPServerEventDeleteParam>(),
|
||||
loading: new Emitter<MCPServerEventLoadingParam>(),
|
||||
onAdd: new Emitter<MCPAddResponse>(),
|
||||
onUpdate: new Emitter<MCPUpdateResponse>(),
|
||||
onDelete: new Emitter<MCPDeleteResponse>(),
|
||||
// onChangeLoading: new Emitter<MCPLoadingResponse>(),
|
||||
}
|
||||
// toolCall: {
|
||||
// success: new Emitter<void>(),
|
||||
// error: new Emitter<void>(),
|
||||
// },
|
||||
} satisfies {
|
||||
[event in 'serverEvent']: {
|
||||
add: Emitter<MCPServerEventAddParam>,
|
||||
update: Emitter<MCPServerEventUpdateParam>,
|
||||
delete: Emitter<MCPServerEventDeleteParam>,
|
||||
loading: Emitter<MCPServerEventLoadingParam>,
|
||||
serverEvent: {
|
||||
onAdd: Emitter<MCPAddResponse>,
|
||||
onUpdate: Emitter<MCPUpdateResponse>,
|
||||
onDelete: Emitter<MCPDeleteResponse>,
|
||||
// onChangeLoading: Emitter<MCPLoadingResponse>,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,10 +78,10 @@ export class MCPChannel implements IServerChannel {
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
|
||||
// server events
|
||||
if (event === 'onAdd_server') return this.mcpEmitters.serverEvent.add.event;
|
||||
else if (event === 'onUpdate_server') return this.mcpEmitters.serverEvent.update.event;
|
||||
else if (event === 'onDelete_server') return this.mcpEmitters.serverEvent.delete.event;
|
||||
else if (event === 'onLoading_server') return this.mcpEmitters.serverEvent.loading.event;
|
||||
if (event === 'onAdd_server') return this.mcpEmitters.serverEvent.onAdd.event;
|
||||
else if (event === 'onUpdate_server') return this.mcpEmitters.serverEvent.onUpdate.event;
|
||||
else if (event === 'onDelete_server') return this.mcpEmitters.serverEvent.onDelete.event;
|
||||
// else if (event === 'onLoading_server') return this.mcpEmitters.serverEvent.onChangeLoading.event;
|
||||
|
||||
// handle unknown events
|
||||
else throw new Error(`Event not found: ${event}`);
|
||||
@@ -72,29 +91,30 @@ export class MCPChannel implements IServerChannel {
|
||||
async call(_: unknown, command: string, params: any): Promise<any> {
|
||||
try {
|
||||
if (command === 'setupServers') {
|
||||
await this._callSetupServers(params)
|
||||
await this._setupServers(params)
|
||||
}
|
||||
else if (command === 'closeAllServers') {
|
||||
await this._callCloseAllServers()
|
||||
await this._closeAllServers()
|
||||
}
|
||||
else if (command === 'toggleServer') {
|
||||
await this._handleToggleServer(params.serverName, params.isOn)
|
||||
}
|
||||
else if (command === 'callTool') {
|
||||
// TODO: HANDLE THIS
|
||||
await this._toggleServer(params.serverName, params.isOn)
|
||||
}
|
||||
// TODO!!! is this still needed?
|
||||
// else if (command === 'callTool') {
|
||||
// // TODO: HANDLE THIS
|
||||
// }
|
||||
else {
|
||||
throw new Error(`Void sendLLM: command "${command}" not recognized.`)
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.log('mcp channel: Call Error:', e)
|
||||
console.error('mcp channel: Call Error:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// call functions
|
||||
|
||||
private async _callSetupServers(params: { mcpConfig: MCPConfig, serverStates: MCPServerStates }) {
|
||||
private async _setupServers(params: { mcpConfig: MCPConfigFileType, serverStates: MCPServerStateOfName }) {
|
||||
|
||||
const { mcpConfig, serverStates } = params
|
||||
|
||||
@@ -118,27 +138,27 @@ export class MCPChannel implements IServerChannel {
|
||||
// Divide the server based on event
|
||||
const addedServers = serverNames.filter((serverName) => {
|
||||
const { prevMCPConfig, newMCPConfig } = getPrevAndNewServerConfig(serverName)
|
||||
const isAdded = !prevMCPConfig && newMCPConfig
|
||||
if (isAdded) {
|
||||
this.mcpEmitters.serverEvent.loading.fire(this._getLoadingServerObject(serverName, serverStates[serverName]?.isOn))
|
||||
}
|
||||
return isAdded
|
||||
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 isUpdated = prevMCPConfig && newMCPConfig && !equals(prevMCPConfig, newMCPConfig)
|
||||
if (isUpdated) {
|
||||
this.mcpEmitters.serverEvent.loading.fire(this._getLoadingServerObject(serverName, serverStates[serverName]?.isOn))
|
||||
}
|
||||
return isUpdated
|
||||
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 isDeleted = prevMCPConfig && !newMCPConfig
|
||||
if (isDeleted) {
|
||||
this.mcpEmitters.serverEvent.loading.fire(this._getLoadingServerObject(serverName, serverStates[serverName]?.isOn))
|
||||
}
|
||||
return isDeleted
|
||||
const isNew = prevMCPConfig && !newMCPConfig
|
||||
// if (isDeleted) {
|
||||
// this.mcpEmitters.serverEvent.onChangeLoading.fire(getLoadingServerObject(serverName, serverStates[serverName]?.isOn))
|
||||
// }
|
||||
return isNew
|
||||
})
|
||||
|
||||
// Check if no changes were made
|
||||
@@ -148,55 +168,55 @@ export class MCPChannel implements IServerChannel {
|
||||
}
|
||||
|
||||
if (addedServers.length > 0) {
|
||||
// Handle added servers
|
||||
const addPromises: Promise<MCPAddResponse>[] = addedServers.map(async (serverName) => {
|
||||
// emit added servers
|
||||
const addPromises = addedServers.map(async (serverName) => {
|
||||
const addedServer = await this._safeSetupServer(mcpServers[serverName], serverName, serverStates[serverName]?.isOn)
|
||||
return {
|
||||
event: 'add',
|
||||
type: 'add',
|
||||
newServer: addedServer,
|
||||
name: serverName,
|
||||
} as MCPAddResponse
|
||||
} as const
|
||||
});
|
||||
const formattedAddedResponses = await Promise.all(addPromises);
|
||||
formattedAddedResponses.forEach((formattedResponse) => (this.mcpEmitters.serverEvent.add.fire({ response: formattedResponse })));
|
||||
formattedAddedResponses.forEach((formattedResponse) => (this.mcpEmitters.serverEvent.onAdd.fire({ response: formattedResponse })));
|
||||
}
|
||||
|
||||
if (updatedServers.length > 0) {
|
||||
// Handle updated servers
|
||||
const updatePromises: Promise<MCPUpdateResponse>[] = updatedServers.map(async (serverName) => {
|
||||
// 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)
|
||||
return {
|
||||
type: 'update',
|
||||
prevServer,
|
||||
newServer: newServer,
|
||||
event: 'update',
|
||||
name: serverName,
|
||||
} as MCPUpdateResponse
|
||||
} as const
|
||||
});
|
||||
const formattedUpdatedResponses = await Promise.all(updatePromises);
|
||||
formattedUpdatedResponses.forEach((formattedResponse) => (this.mcpEmitters.serverEvent.update.fire({ response: formattedResponse })));
|
||||
formattedUpdatedResponses.forEach((formattedResponse) => (this.mcpEmitters.serverEvent.onUpdate.fire({ response: formattedResponse })));
|
||||
}
|
||||
|
||||
if (deletedServers.length > 0) {
|
||||
// Handle deleted servers
|
||||
const deletePromises: Promise<MCPDeleteResponse>[] = deletedServers.map(async (serverName) => {
|
||||
// emit deleted servers
|
||||
const deletePromises = deletedServers.map(async (serverName) => {
|
||||
const prevServer = this.clients[serverName]?.formattedServer;
|
||||
await this._callCloseServer(serverName)
|
||||
this._callRemoveServer(serverName)
|
||||
await this._closeServer(serverName)
|
||||
this._removeServer(serverName)
|
||||
return {
|
||||
event: 'delete',
|
||||
type: 'delete',
|
||||
prevServer,
|
||||
name: serverName,
|
||||
} as MCPDeleteResponse
|
||||
} as const
|
||||
});
|
||||
const formattedDeletedResponses = await Promise.all(deletePromises);
|
||||
formattedDeletedResponses.forEach((formattedResponse) => (this.mcpEmitters.serverEvent.delete.fire({ response: formattedResponse })));
|
||||
formattedDeletedResponses.forEach((formattedResponse) => (this.mcpEmitters.serverEvent.onDelete.fire({ response: formattedResponse })));
|
||||
}
|
||||
}
|
||||
|
||||
private async _callSetupServer(server: MCPServerConfig, serverName: string, isOn = true) {
|
||||
private async _callSetupServer(server: MCPConfigFileServerType, serverName: string, isOn = true) {
|
||||
|
||||
const clientConfig = this.getClientConfig(serverName)
|
||||
const clientConfig = getClientConfig(serverName)
|
||||
const client = new Client(clientConfig)
|
||||
let transport: Transport;
|
||||
let formattedServer: MCPServerModel;
|
||||
@@ -263,7 +283,7 @@ export class MCPChannel implements IServerChannel {
|
||||
}
|
||||
|
||||
// Helper function to safely setup a server
|
||||
private async _safeSetupServer(serverConfig: MCPServerConfig, serverName: string, isOn = true) {
|
||||
private async _safeSetupServer(serverConfig: MCPConfigFileServerType, serverName: string, isOn = true) {
|
||||
try {
|
||||
return await this._callSetupServer(serverConfig, serverName, isOn)
|
||||
} catch (err) {
|
||||
@@ -293,15 +313,15 @@ export class MCPChannel implements IServerChannel {
|
||||
}
|
||||
}
|
||||
|
||||
private async _callCloseAllServers() {
|
||||
private async _closeAllServers() {
|
||||
for (const serverName in this.clients) {
|
||||
await this._callCloseServer(serverName)
|
||||
this._callRemoveServer(serverName)
|
||||
await this._closeServer(serverName)
|
||||
this._removeServer(serverName)
|
||||
}
|
||||
console.log('Closed all MCP servers');
|
||||
}
|
||||
|
||||
private async _callCloseServer(serverName: string) {
|
||||
private async _closeServer(serverName: string) {
|
||||
const server = this.clients[serverName]
|
||||
if (server) {
|
||||
const { client } = server
|
||||
@@ -314,22 +334,22 @@ export class MCPChannel implements IServerChannel {
|
||||
}
|
||||
}
|
||||
|
||||
private _callRemoveServer(serverName: string) {
|
||||
private _removeServer(serverName: string) {
|
||||
if (this.clients[serverName]) {
|
||||
delete this.clients[serverName]
|
||||
console.log(`Removed MCP server ${serverName}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleToggleServer(serverName: string, isOn: boolean) {
|
||||
private async _toggleServer(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))
|
||||
// this.mcpEmitters.serverEvent.onChangeLoading.fire(getLoadingServerObject(serverName, isOn))
|
||||
const formattedServer = await this._callSetupServer(this.clients[serverName].mcpConfig, serverName)
|
||||
this.mcpEmitters.serverEvent.update.fire({
|
||||
this.mcpEmitters.serverEvent.onUpdate.fire({
|
||||
response: {
|
||||
event: 'update',
|
||||
type: 'update',
|
||||
name: serverName,
|
||||
newServer: formattedServer,
|
||||
prevServer: prevServer,
|
||||
@@ -337,11 +357,11 @@ export class MCPChannel implements IServerChannel {
|
||||
})
|
||||
} else {
|
||||
// Handle turning off the server
|
||||
this.mcpEmitters.serverEvent.loading.fire(this._getLoadingServerObject(serverName, isOn))
|
||||
this._callCloseServer(serverName)
|
||||
this.mcpEmitters.serverEvent.update.fire({
|
||||
// this.mcpEmitters.serverEvent.onChangeLoading.fire(getLoadingServerObject(serverName, isOn))
|
||||
this._closeServer(serverName)
|
||||
this.mcpEmitters.serverEvent.onUpdate.fire({
|
||||
response: {
|
||||
event: 'update',
|
||||
type: 'update',
|
||||
name: serverName,
|
||||
newServer: {
|
||||
status: 'offline',
|
||||
@@ -358,21 +378,6 @@ export class MCPChannel implements IServerChannel {
|
||||
}
|
||||
}
|
||||
|
||||
// Util functions
|
||||
|
||||
private _getLoadingServerObject(serverName: string, isOn = true): MCPServerEventLoadingParam {
|
||||
return {
|
||||
response: {
|
||||
event: 'loading',
|
||||
name: serverName,
|
||||
newServer: {
|
||||
status: 'loading',
|
||||
isOn,
|
||||
tools: [],
|
||||
command: '',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user