mirror of
https://github.com/voideditor/void
synced 2026-05-24 01:48:25 +00:00
Created mcpChannel to handle all mcp server setup and events in electron-main
This commit is contained in:
parent
716cb8d591
commit
b18ac4b6c0
1 changed files with 232 additions and 0 deletions
232
src/vs/workbench/contrib/void/electron-main/mcpChannel.ts
Normal file
232
src/vs/workbench/contrib/void/electron-main/mcpChannel.ts
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
/*--------------------------------------------------------------------------------------
|
||||
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
|
||||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
// registered in app.ts
|
||||
// can't make a service responsible for this, because it needs
|
||||
// to be connected to the main process and node dependencies
|
||||
|
||||
import { IServerChannel } from '../../../../base/parts/ipc/common/ipc.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
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, EventMCPServerSetupOnError, EventMCPServerSetupOnSuccess, MCPServerSuccessModel, MCPServerErrorModel } from '../common/mcpServiceTypes.js';
|
||||
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
||||
|
||||
export class MCPChannel implements IServerChannel {
|
||||
|
||||
// connected clients
|
||||
private clients: { [clientId: string]: { client: Client, mcpConfig: MCPServerConfig } } = {}
|
||||
private getClientConfig(serverName: string) {
|
||||
return {
|
||||
name: `${serverName}-client`,
|
||||
version: '0.1.0',
|
||||
// debug: true,
|
||||
}
|
||||
}
|
||||
|
||||
// mcp emitters
|
||||
private readonly mcpEmitters = {
|
||||
serverSetup: {
|
||||
success: new Emitter<EventMCPServerSetupOnSuccess<MCPServerSuccessModel>>(),
|
||||
error: new Emitter<EventMCPServerSetupOnError<MCPServerErrorModel>>(),
|
||||
},
|
||||
// toolCall: {
|
||||
// success: new Emitter<void>(),
|
||||
// error: new Emitter<void>(),
|
||||
// },
|
||||
} satisfies {
|
||||
[task in 'serverSetup']: {
|
||||
success: Emitter<EventMCPServerSetupOnSuccess<any>>,
|
||||
error: Emitter<EventMCPServerSetupOnError<any>>,
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
// private readonly metricsService: IMetricsService,
|
||||
) { }
|
||||
|
||||
// browser uses this to listen for changes
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
|
||||
// server setup
|
||||
if (event === 'onSuccess_serverSetup') return this.mcpEmitters.serverSetup.success.event;
|
||||
else if (event === 'onError_serverSetup') return this.mcpEmitters.serverSetup.error.event;
|
||||
|
||||
// tool call
|
||||
// else if (event === 'onSuccess_toolCall') return this.mcpEmitters.toolCall.success.event;
|
||||
// else if (event === 'onError_toolCall') return this.mcpEmitters.toolCall.error.event;
|
||||
|
||||
// handle unknown events
|
||||
else throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
// browser uses this to call (see this.channel.call() in mcpConfigService.ts for all usages)
|
||||
async call(_: unknown, command: string, params: any): Promise<any> {
|
||||
try {
|
||||
if (command === 'setupServers') {
|
||||
await this._callSetupServers(params)
|
||||
}
|
||||
else if (command === 'closeAllServers') {
|
||||
await this._callCloseAllServers()
|
||||
}
|
||||
else if (command === 'toggleServer') {
|
||||
// TODO: HANDLE THIS
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// call functions
|
||||
|
||||
private async _callSetupServers(mcpConfig: MCPConfig) {
|
||||
|
||||
// Reset all servers
|
||||
if (Object.keys(this.clients).length > 0) {
|
||||
await this._callCloseAllServers()
|
||||
}
|
||||
|
||||
// Handle config file setup and changes
|
||||
const { mcpServers } = mcpConfig
|
||||
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
|
||||
}
|
||||
for (const serverName of serverNames) {
|
||||
|
||||
// Get the server config
|
||||
const server = mcpServers[serverName]
|
||||
|
||||
if (server) {
|
||||
// TODO: add a check if server is on or off
|
||||
try {
|
||||
await this._callSetupServer(server, serverName)
|
||||
|
||||
} catch (err) {
|
||||
// catches *any* error (including SSE fallback or Stdio connect)
|
||||
console.error(`❌ Failed to connect to server "${serverName}":`, err);
|
||||
// fire error event
|
||||
// TODO: handle sending back the error
|
||||
const typedErr = err as Error
|
||||
console.log('Error Message: ', typedErr.message)
|
||||
this.mcpEmitters.serverSetup.error.fire({
|
||||
model: {
|
||||
serverName,
|
||||
isLive: false,
|
||||
isOn: false,
|
||||
tools: [],
|
||||
error: typedErr.message,
|
||||
}
|
||||
})
|
||||
// and then move on to the next server
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _callSetupServer(server: MCPServerConfig, serverName: string) {
|
||||
|
||||
const clientConfig = this.getClientConfig(serverName)
|
||||
const client = new Client(clientConfig)
|
||||
let transport: Transport;
|
||||
|
||||
if (server.url) {
|
||||
// first try HTTP, fall back to SSE
|
||||
try {
|
||||
transport = new StreamableHTTPClientTransport(server.url);
|
||||
await client.connect(transport);
|
||||
console.log(`Connected via HTTP to ${serverName}`);
|
||||
} catch (httpErr) {
|
||||
console.warn(`HTTP failed for ${serverName}, trying SSE…`, httpErr);
|
||||
transport = new SSEClientTransport(server.url);
|
||||
await client.connect(transport);
|
||||
console.log(`Connected via SSE to ${serverName}`);
|
||||
}
|
||||
} else if (server.command) {
|
||||
console.log('ENV DATA: ', server.env)
|
||||
transport = new StdioClientTransport({
|
||||
command: server.command,
|
||||
args: server.args,
|
||||
env: {
|
||||
...server.env,
|
||||
...process.env
|
||||
} as Record<string, string>,
|
||||
});
|
||||
|
||||
client.onerror = (err) => {
|
||||
// TODO: HANDLE SENDING AN EVENT BACK TO THE CLIENT
|
||||
console.error(`Error in MCP client for ${serverName}:`, err);
|
||||
}
|
||||
|
||||
await client.connect(transport)
|
||||
|
||||
console.log(`Connected via Stdio to ${serverName}`);
|
||||
|
||||
const { tools } = await client.listTools()
|
||||
|
||||
this.mcpEmitters.serverSetup.success.fire({
|
||||
model: {
|
||||
serverName,
|
||||
isLive: true,
|
||||
isOn: true,
|
||||
tools: tools,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.warn(`No url or command for server ${serverName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// only add to clients map if connect succeeded
|
||||
this.clients[serverName] = { client, mcpConfig: server };
|
||||
}
|
||||
|
||||
private async _callCloseAllServers() {
|
||||
for (const serverName in this.clients) {
|
||||
await this._callCloseServer(serverName)
|
||||
}
|
||||
console.log('Closed all MCP servers');
|
||||
}
|
||||
|
||||
private async _callCloseServer(serverName: string) {
|
||||
if (this.clients[serverName]) {
|
||||
const { client } = this.clients[serverName]
|
||||
await client.close()
|
||||
delete this.clients[serverName]
|
||||
console.log(`Closed MCP server ${serverName}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// listen functions
|
||||
|
||||
// private _onServerSetupSuccess(serverName: string) {
|
||||
// this.mcpEmitters.serverSetup.success.fire()
|
||||
// }
|
||||
// private _onServerSetupError(error: Error) {
|
||||
// // this.error = error
|
||||
// console.log('WHAAAAT')
|
||||
// console.log('Error in MCPChannel:', error)
|
||||
// }
|
||||
// private _onToolCallSuccess(serverName: string) {
|
||||
// this.mcpEmitters.toolCall.success.fire()
|
||||
// }
|
||||
// private _onToolCallError(serverName: string) {
|
||||
// this.mcpEmitters.toolCall.error.fire()
|
||||
// }
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue