mirror of
https://github.com/voideditor/void
synced 2026-05-23 17:38:23 +00:00
clearer types
This commit is contained in:
parent
57bc32ac9b
commit
89ac548ae9
4 changed files with 91 additions and 109 deletions
|
|
@ -19,7 +19,7 @@ import { ToolApprovalType, toolApprovalTypes } from '../../../../common/toolsSer
|
|||
import Severity from '../../../../../../../base/common/severity.js'
|
||||
import { getModelCapabilities, modelOverrideKeys, ModelOverrides } from '../../../../common/modelCapabilities.js';
|
||||
import { TransferEditorType, TransferFilesInfo } from '../../../extensionTransferTypes.js';
|
||||
import { MCPServerObject } from '../../../../common/mcpServiceTypes.js';
|
||||
import { MCPServer } from '../../../../common/mcpServiceTypes.js';
|
||||
import { useMCPServiceState } from '../util/services.js';
|
||||
|
||||
const ButtonLeftTextRightOption = ({ text, leftButton }: { text: string, leftButton?: React.ReactNode }) => {
|
||||
|
|
@ -901,7 +901,7 @@ export const OneClickSwitchButton = ({ fromEditor = 'VS Code', className = '' }:
|
|||
// full settings
|
||||
|
||||
// MCP Server component
|
||||
const MCPServer = ({ name, server }: { name: string, server: MCPServerObject }) => {
|
||||
const MCPServer = ({ name, server }: { name: string, server: MCPServer }) => {
|
||||
const accessor = useAccessor();
|
||||
const mcpService = accessor.get('IMCPService');
|
||||
|
||||
|
|
@ -942,8 +942,8 @@ const MCPServer = ({ name, server }: { name: string, server: MCPServerObject })
|
|||
{/* Tools section */}
|
||||
<div className="mt-1 mx-4">
|
||||
<div className="flex flex-wrap gap-2 max-h-32 overflow-y-auto pb-1">
|
||||
{isOn && server.tools.length > 0 ? (
|
||||
server.tools.map((tool: { name: string; description?: string }) => (
|
||||
{isOn && (server.tools ?? []).length > 0 ? (
|
||||
(server.tools ?? []).map((tool: { name: string; description?: string }) => (
|
||||
<span
|
||||
key={tool.name}
|
||||
className="px-2 py-0.5 bg-black/5 dark:bg-white/5 rounded text-xs"
|
||||
|
|
|
|||
|
|
@ -14,14 +14,14 @@ import { IProductService } from '../../../../platform/product/common/productServ
|
|||
import { VSBuffer } from '../../../../base/common/buffer.js';
|
||||
import { IChannel } from '../../../../base/parts/ipc/common/ipc.js';
|
||||
import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js';
|
||||
import { MCPServerOfName, MCPConfigFileType, MCPAddServerResponse, MCPUpdateServerResponse, MCPDeleteServerResponse, MCPServerObject, MCPToolCallParams, MCPGenericToolResponse } from './mcpServiceTypes.js';
|
||||
import { MCPServerOfName, MCPConfigFileJSON, MCPAddServerResponse, MCPUpdateServerResponse, MCPDeleteServerResponse, MCPServer, MCPToolCallParams, MCPGenericToolResponse } from './mcpServiceTypes.js';
|
||||
import { Event, Emitter } from '../../../../base/common/event.js';
|
||||
import { InternalToolInfo } from './prompt/prompts.js';
|
||||
import { IVoidSettingsService } from './voidSettingsService.js';
|
||||
import { MCPUserStateOfName } from './voidSettingsTypes.js';
|
||||
|
||||
|
||||
type MCPState = {
|
||||
type MCPServiceState = {
|
||||
mcpServerOfName: MCPServerOfName,
|
||||
error: string | undefined, // global parsing error
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ export interface IMCPService {
|
|||
revealMCPConfigFile(): Promise<void>;
|
||||
toggleMCPServer(serverName: string, isOn: boolean): Promise<void>;
|
||||
|
||||
readonly state: MCPState; // NOT persisted
|
||||
readonly state: MCPServiceState; // NOT persisted
|
||||
onDidChangeState: Event<void>;
|
||||
|
||||
getCurrentMCPToolNames(): InternalToolInfo[];
|
||||
|
|
@ -73,7 +73,7 @@ class MCPService extends Disposable implements IMCPService {
|
|||
private readonly channel: IChannel // MCPChannel
|
||||
|
||||
// list of MCP servers pulled from mcpChannel
|
||||
state: MCPState = {
|
||||
state: MCPServiceState = {
|
||||
mcpServerOfName: {},
|
||||
error: undefined,
|
||||
}
|
||||
|
|
@ -122,7 +122,7 @@ class MCPService extends Disposable implements IMCPService {
|
|||
}
|
||||
}
|
||||
|
||||
private readonly _setMCPServerState = async (serverName: string, newServer: MCPServerObject | undefined) => {
|
||||
private readonly _setMCPServerState = async (serverName: string, newServer: MCPServer | undefined) => {
|
||||
this.state = {
|
||||
...this.state,
|
||||
mcpServerOfName: {
|
||||
|
|
@ -180,7 +180,7 @@ class MCPService extends Disposable implements IMCPService {
|
|||
|
||||
public getCurrentMCPToolNames(): InternalToolInfo[] {
|
||||
const allTools = Object.entries(this.state.mcpServerOfName).flatMap(([serverName, server]) => {
|
||||
return server.tools.map(tool => {
|
||||
return server.tools?.map(tool => {
|
||||
// Convert JsonSchema to the expected format
|
||||
const convertedParams: { [paramName: string]: { description: string } } = {};
|
||||
|
||||
|
|
@ -200,7 +200,7 @@ class MCPService extends Disposable implements IMCPService {
|
|||
serverName,
|
||||
};
|
||||
});
|
||||
});
|
||||
}).filter(s => s !== undefined)
|
||||
return allTools;
|
||||
}
|
||||
|
||||
|
|
@ -229,7 +229,7 @@ class MCPService extends Disposable implements IMCPService {
|
|||
}
|
||||
|
||||
|
||||
private async _parseMCPConfigFile(): Promise<MCPConfigFileType | null> {
|
||||
private async _parseMCPConfigFile(): Promise<MCPConfigFileJSON | null> {
|
||||
const mcpConfigUri = await this._getMCPConfigFilePath();
|
||||
try {
|
||||
const fileContent = await this.fileService.readFile(mcpConfigUri);
|
||||
|
|
@ -238,7 +238,7 @@ class MCPService extends Disposable implements IMCPService {
|
|||
if (!configFileJson.mcpServers) {
|
||||
throw new Error('Missing mcpServers property');
|
||||
}
|
||||
return configFileJson as MCPConfigFileType;
|
||||
return configFileJson as MCPConfigFileJSON;
|
||||
} catch (error) {
|
||||
const fullError = `Error parsing MCP config file: ${error}`;
|
||||
this._setHasError(fullError)
|
||||
|
|
@ -281,7 +281,7 @@ class MCPService extends Disposable implements IMCPService {
|
|||
})
|
||||
}
|
||||
|
||||
this.channel.call('refreshMCPServers', { mcpConfig: newConfigFileJSON, userStateOfName: this.voidSettingsService.state.mcpUserStateOfName })
|
||||
this.channel.call('refreshMCPServers', { mcpConfigFileJSON: newConfigFileJSON, userStateOfName: this.voidSettingsService.state.mcpUserStateOfName })
|
||||
}
|
||||
|
||||
public async callMCPTool(toolData: MCPToolCallParams): Promise<MCPGenericToolResponse> {
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ export interface MCPTool {
|
|||
|
||||
// MCP SERVER CONFIG FILE TYPES -----------------------------
|
||||
|
||||
export interface MCPConfigFileServerType {
|
||||
export interface MCPConfigFileEntryJSON {
|
||||
// Command-based server properties
|
||||
command?: string;
|
||||
args?: string[];
|
||||
|
|
@ -127,73 +127,60 @@ export interface MCPConfigFileServerType {
|
|||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface MCPConfigFileType {
|
||||
mcpServers: Record<string, MCPConfigFileServerType>;
|
||||
export interface MCPConfigFileJSON {
|
||||
mcpServers: Record<string, MCPConfigFileEntryJSON>;
|
||||
}
|
||||
|
||||
|
||||
// SERVER EVENT TYPES ------------------------------------------
|
||||
|
||||
export interface MCPServerObject {
|
||||
export type MCPServer = {
|
||||
// Command-based server properties
|
||||
tools: MCPTool[],
|
||||
status: 'loading' | 'error' | 'success' | 'offline',
|
||||
status: 'loading' | 'success' | 'offline',
|
||||
command?: string,
|
||||
error?: string,
|
||||
} | {
|
||||
tools?: undefined,
|
||||
status: 'error',
|
||||
command?: string,
|
||||
error: string,
|
||||
}
|
||||
|
||||
export interface MCPServerOfName {
|
||||
[serverName: string]: MCPServerObject;
|
||||
[serverName: string]: MCPServer;
|
||||
}
|
||||
|
||||
// Create separate types for success and error cases
|
||||
export type MCPServerSuccessModel = MCPServerObject;
|
||||
export type MCPServerErrorModel = Omit<MCPServerObject, 'error'> & { error: string };
|
||||
|
||||
|
||||
export type MCPServerSetupParams = {
|
||||
serverName: string;
|
||||
onSuccess: (param: { model: MCPServerSuccessModel & { serverName: string } }) => void;
|
||||
onError: (param: { model: MCPServerErrorModel & { serverName: string } }) => void;
|
||||
}
|
||||
|
||||
// Listener event types
|
||||
export type EventMCPServerSetupOnSuccess = Parameters<MCPServerSetupParams['onSuccess']>[0]
|
||||
export type EventMCPServerSetupOnError = Parameters<MCPServerSetupParams['onError']>[0]
|
||||
|
||||
export type MCPServerModel = MCPServerSuccessModel | MCPServerErrorModel;
|
||||
|
||||
|
||||
export type MCPServerEventType = {
|
||||
export type MCPServerEvent = {
|
||||
type: 'add';
|
||||
name: string;
|
||||
prevServer?: undefined;
|
||||
newServer: MCPServerModel;
|
||||
newServer: MCPServer;
|
||||
} | {
|
||||
type: 'update';
|
||||
name: string;
|
||||
prevServer: MCPServerModel;
|
||||
newServer: MCPServerModel;
|
||||
prevServer: MCPServer;
|
||||
newServer: MCPServer;
|
||||
} | {
|
||||
type: 'delete';
|
||||
name: string;
|
||||
newServer?: undefined;
|
||||
prevServer: MCPServerModel;
|
||||
prevServer: MCPServer;
|
||||
} | {
|
||||
type: 'loading';
|
||||
name: string;
|
||||
prevServer?: undefined;
|
||||
newServer: MCPServerModel;
|
||||
newServer: MCPServer;
|
||||
}
|
||||
|
||||
// Response types
|
||||
export type MCPAddServerResponse = { response: MCPServerEventType & { type: 'add' } }
|
||||
export type MCPUpdateServerResponse = { response: MCPServerEventType & { type: 'update' } }
|
||||
export type MCPDeleteServerResponse = { response: MCPServerEventType & { type: 'delete' } }
|
||||
// export type MCPLoadingChangeResponse = { response: MCPEventType & { type: 'loading' } }
|
||||
export type MCPServerEventResponse = { response: MCPServerEvent }
|
||||
|
||||
export type MCPAddServerResponse = { response: MCPServerEvent & { type: 'add' } }
|
||||
export type MCPUpdateServerResponse = { response: MCPServerEvent & { type: 'update' } }
|
||||
export type MCPDeleteServerResponse = { response: MCPServerEvent & { type: 'delete' } }
|
||||
|
||||
|
||||
export type MCPServerEventResponse = { response: MCPServerEventType }
|
||||
|
||||
export interface MCPConfigFileParseErrorResponse {
|
||||
response: {
|
||||
|
|
|
|||
|
|
@ -13,28 +13,12 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
||||
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
||||
import { MCPConfigFileType, MCPConfigFileServerType, MCPServerErrorModel, MCPServerModel, MCPAddServerResponse, MCPUpdateServerResponse, MCPDeleteServerResponse, MCPGenericToolResponse, MCPToolErrorResponse } from '../common/mcpServiceTypes.js';
|
||||
import { MCPConfigFileJSON, MCPConfigFileEntryJSON, MCPServer, MCPAddServerResponse, MCPUpdateServerResponse, MCPDeleteServerResponse, MCPGenericToolResponse, MCPToolErrorResponse } from '../common/mcpServiceTypes.js';
|
||||
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
||||
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
||||
import { equals } from '../../../../base/common/objects.js';
|
||||
import { MCPUserStateOfName } from '../common/voidSettingsTypes.js';
|
||||
|
||||
|
||||
// const getLoadingServerObject = (serverName: string, isOn: boolean | undefined) => {
|
||||
// return {
|
||||
// response: {
|
||||
// event: 'loading',
|
||||
// name: serverName,
|
||||
// newServer: {
|
||||
// status: 'loading',
|
||||
// isOn,
|
||||
// tools: [],
|
||||
// command: '',
|
||||
// }
|
||||
// }
|
||||
// } as const
|
||||
// }
|
||||
|
||||
const getClientConfig = (serverName: string) => {
|
||||
return {
|
||||
name: `${serverName}-client`,
|
||||
|
|
@ -44,10 +28,26 @@ const getClientConfig = (serverName: string) => {
|
|||
}
|
||||
|
||||
|
||||
|
||||
type MCPServerNonError = MCPServer & { status: Omit<MCPServer['status'], 'error'> }
|
||||
type MCPServerError = MCPServer & { status: 'error' }
|
||||
|
||||
|
||||
|
||||
export class MCPChannel implements IServerChannel {
|
||||
|
||||
// connected clients
|
||||
private clients: { [clientId: string]: { client?: Client, mcpConfig: MCPConfigFileServerType, formattedServer: MCPServerModel } } = {}
|
||||
private infoOfClientId: {
|
||||
[clientId: string]: {
|
||||
_client: Client,
|
||||
mcpConfig: MCPConfigFileEntryJSON,
|
||||
mcpServer: MCPServerNonError,
|
||||
} | {
|
||||
_client?: undefined,
|
||||
mcpConfig: MCPConfigFileEntryJSON,
|
||||
mcpServer: MCPServerError,
|
||||
}
|
||||
} = {}
|
||||
|
||||
// mcp emitters
|
||||
private readonly mcpEmitters = {
|
||||
|
|
@ -118,21 +118,17 @@ export class MCPChannel implements IServerChannel {
|
|||
|
||||
// server functions
|
||||
|
||||
private async _refreshMCPServers(params: { mcpConfig: MCPConfigFileType, userStateOfName: MCPUserStateOfName }) {
|
||||
private async _refreshMCPServers(params: { mcpConfigFileJSON: MCPConfigFileJSON, userStateOfName: MCPUserStateOfName }) {
|
||||
|
||||
const { mcpConfig, userStateOfName } = params
|
||||
const { mcpConfigFileJSON, userStateOfName } = params
|
||||
|
||||
// Get all prevServers
|
||||
const prevServers = { ...this.clients }
|
||||
const prevServers = { ...this.infoOfClientId }
|
||||
|
||||
// Handle config file setup and changes
|
||||
const { mcpServers } = mcpConfig
|
||||
const { mcpServers } = mcpConfigFileJSON
|
||||
const serverNames = Object.keys(mcpServers)
|
||||
if (serverNames.length === 0) {
|
||||
// TODO: CHANGE THIS TO AN ERROR EVENT
|
||||
console.log('No MCP servers found in config file.')
|
||||
return
|
||||
}
|
||||
|
||||
const getPrevAndNewServerConfig = (serverName: string) => {
|
||||
const prevMCPConfig = prevServers[serverName]?.mcpConfig
|
||||
const newMCPConfig = mcpServers[serverName]
|
||||
|
|
@ -188,7 +184,7 @@ export class MCPChannel implements IServerChannel {
|
|||
if (updatedServers.length > 0) {
|
||||
// emit updated servers
|
||||
const updatePromises = updatedServers.map(async (serverName) => {
|
||||
const prevServer = this.clients[serverName]?.formattedServer;
|
||||
const prevServer = this.infoOfClientId[serverName]?.mcpServer;
|
||||
const newServer = await this._safeSetupServer(mcpServers[serverName], serverName, userStateOfName[serverName]?.isOn)
|
||||
return {
|
||||
type: 'update',
|
||||
|
|
@ -204,9 +200,9 @@ export class MCPChannel implements IServerChannel {
|
|||
if (deletedServers.length > 0) {
|
||||
// emit deleted servers
|
||||
const deletePromises = deletedServers.map(async (serverName) => {
|
||||
const prevServer = this.clients[serverName]?.formattedServer;
|
||||
await this._closeServer(serverName)
|
||||
this._removeServer(serverName)
|
||||
const prevServer = this.infoOfClientId[serverName]?.mcpServer;
|
||||
await this._closeClient(serverName)
|
||||
this._removeClient(serverName)
|
||||
return {
|
||||
type: 'delete',
|
||||
prevServer,
|
||||
|
|
@ -218,12 +214,12 @@ export class MCPChannel implements IServerChannel {
|
|||
}
|
||||
}
|
||||
|
||||
private async _callSetupServer(server: MCPConfigFileServerType, serverName: string, isOn = true) {
|
||||
private async _callSetupServer(server: MCPConfigFileEntryJSON, serverName: string, isOn = true) {
|
||||
|
||||
const clientConfig = getClientConfig(serverName)
|
||||
const client = new Client(clientConfig)
|
||||
let transport: Transport;
|
||||
let formattedServer: MCPServerModel;
|
||||
let formattedServer: MCPServer;
|
||||
|
||||
if (server.url) {
|
||||
// first try HTTP, fall back to SSE
|
||||
|
|
@ -279,12 +275,12 @@ export class MCPChannel implements IServerChannel {
|
|||
}
|
||||
|
||||
|
||||
this.clients[serverName] = { client, mcpConfig: server, formattedServer }
|
||||
this.infoOfClientId[serverName] = { _client: client, mcpConfig: server, mcpServer: formattedServer }
|
||||
return formattedServer;
|
||||
}
|
||||
|
||||
// Helper function to safely setup a server
|
||||
private async _safeSetupServer(serverConfig: MCPConfigFileServerType, serverName: string, isOn = true) {
|
||||
private async _safeSetupServer(serverConfig: MCPConfigFileEntryJSON, serverName: string, isOn = true) {
|
||||
try {
|
||||
return await this._callSetupServer(serverConfig, serverName, isOn)
|
||||
} catch (err) {
|
||||
|
|
@ -296,17 +292,17 @@ export class MCPChannel implements IServerChannel {
|
|||
fullCommand = `${serverConfig.command} ${serverConfig.args?.join(' ') || ''}`
|
||||
}
|
||||
|
||||
const formattedError: MCPServerErrorModel = {
|
||||
const formattedError: MCPServerError = {
|
||||
status: 'error',
|
||||
tools: [],
|
||||
// tools: [],
|
||||
error: typedErr.message,
|
||||
command: fullCommand,
|
||||
}
|
||||
|
||||
// Add the error to the clients object
|
||||
this.clients[serverName] = {
|
||||
this.infoOfClientId[serverName] = {
|
||||
mcpConfig: serverConfig,
|
||||
formattedServer: formattedError,
|
||||
mcpServer: formattedError,
|
||||
}
|
||||
|
||||
return formattedError
|
||||
|
|
@ -314,39 +310,38 @@ export class MCPChannel implements IServerChannel {
|
|||
}
|
||||
|
||||
private async _closeAllMCPServers() {
|
||||
for (const serverName in this.clients) {
|
||||
await this._closeServer(serverName)
|
||||
this._removeServer(serverName)
|
||||
for (const serverName in this.infoOfClientId) {
|
||||
await this._closeClient(serverName)
|
||||
this._removeClient(serverName)
|
||||
}
|
||||
console.log('Closed all MCP servers');
|
||||
}
|
||||
|
||||
private async _closeServer(serverName: string) {
|
||||
const server = this.clients[serverName]
|
||||
if (server) {
|
||||
const { client } = server
|
||||
if (client) {
|
||||
await client.close()
|
||||
}
|
||||
// Remove the client from the clients object
|
||||
delete this.clients[serverName].client
|
||||
console.log(`Closed MCP server ${serverName}`);
|
||||
private async _closeClient(serverName: string) {
|
||||
const info = this.infoOfClientId[serverName]
|
||||
if (!info) return
|
||||
const { _client: client } = info
|
||||
if (client) {
|
||||
await client.close()
|
||||
}
|
||||
// Remove the client from the clients object
|
||||
delete this.infoOfClientId[serverName]._client
|
||||
console.log(`Closed MCP server ${serverName}`);
|
||||
}
|
||||
|
||||
private _removeServer(serverName: string) {
|
||||
if (this.clients[serverName]) {
|
||||
delete this.clients[serverName]
|
||||
private _removeClient(serverName: string) {
|
||||
if (this.infoOfClientId[serverName]) {
|
||||
delete this.infoOfClientId[serverName]
|
||||
console.log(`Removed MCP server ${serverName}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async _toggleMCPServer(serverName: string, isOn: boolean) {
|
||||
const prevServer = this.clients[serverName]?.formattedServer
|
||||
const prevServer = this.infoOfClientId[serverName]?.mcpServer
|
||||
if (isOn) {
|
||||
// Handle turning on the server
|
||||
// this.mcpEmitters.serverEvent.onChangeLoading.fire(getLoadingServerObject(serverName, isOn))
|
||||
const formattedServer = await this._callSetupServer(this.clients[serverName].mcpConfig, serverName)
|
||||
const formattedServer = await this._callSetupServer(this.infoOfClientId[serverName].mcpConfig, serverName)
|
||||
this.mcpEmitters.serverEvent.onUpdate.fire({
|
||||
response: {
|
||||
type: 'update',
|
||||
|
|
@ -358,7 +353,7 @@ export class MCPChannel implements IServerChannel {
|
|||
} else {
|
||||
// Handle turning off the server
|
||||
// this.mcpEmitters.serverEvent.onChangeLoading.fire(getLoadingServerObject(serverName, isOn))
|
||||
this._closeServer(serverName)
|
||||
this._closeClient(serverName)
|
||||
this.mcpEmitters.serverEvent.onUpdate.fire({
|
||||
response: {
|
||||
type: 'update',
|
||||
|
|
@ -380,9 +375,9 @@ export class MCPChannel implements IServerChannel {
|
|||
// tool call functions
|
||||
|
||||
private async _callTool(serverName: string, toolName: string, params: any): Promise<MCPGenericToolResponse> {
|
||||
const server = this.clients[serverName]
|
||||
const server = this.infoOfClientId[serverName]
|
||||
if (!server) throw new Error(`Server ${serverName} not found`)
|
||||
const { client } = server
|
||||
const { _client: client } = server
|
||||
if (!client) throw new Error(`Client for server ${serverName} not found`)
|
||||
|
||||
// Call the tool with the provided parameters
|
||||
|
|
|
|||
Loading…
Reference in a new issue