@kinvolk/headlamp-plugin
Version:
The needed infrastructure for building Headlamp plugins.
334 lines (295 loc) • 9.62 kB
text/typescript
// Frontend MCP client that communicates with Electron main process
// This replaces the direct MCP client import to avoid spawn issues in renderer process
interface MCPTool {
name: string;
description?: string;
inputSchema?: any;
server?: string; // Add server information
}
interface MCPResponse {
success: boolean;
tools?: MCPTool[];
result?: any;
error?: string;
toolCallId?: string;
}
interface ElectronMCPApi {
getTools: () => Promise<MCPResponse>;
executeTool: (
toolName: string,
args: Record<string, any>,
toolCallId?: string
) => Promise<MCPResponse>;
getStatus: () => Promise<{ isInitialized: boolean; hasClient: boolean }>;
resetClient: () => Promise<MCPResponse>;
getConfig: () => Promise<{ success: boolean; config?: any; error?: string }>;
updateConfig: (config: any) => Promise<MCPResponse>;
getToolsConfig: () => Promise<{ success: boolean; config?: any; error?: string }>;
updateToolsConfig: (config: any) => Promise<MCPResponse>;
setToolEnabled: (serverName: string, toolName: string, enabled: boolean) => Promise<MCPResponse>;
getToolStats: (
serverName: string,
toolName: string
) => Promise<{ success: boolean; stats?: any; error?: string }>;
}
// desktopApi is already declared on Window by @kinvolk/headlamp-plugin as `any`.
// We use a helper to access it with proper typing.
function getDesktopMCPApi(): ElectronMCPApi | undefined {
const api = (window as any).desktopApi;
if (api && typeof api.mcp !== 'undefined') {
return api.mcp as ElectronMCPApi;
}
return undefined;
}
// Type augmentation is handled by src/types/electron.d.ts
class ElectronMCPClient {
private isElectron: boolean;
constructor() {
this.isElectron = typeof window !== 'undefined' && getDesktopMCPApi() !== undefined;
}
/**
* Check if running in Electron environment with MCP support
*/
isAvailable(): boolean {
return this.isElectron;
}
/**
* Get available MCP tools from Electron main process
*/
async getTools(): Promise<MCPTool[]> {
if (!this.isElectron) {
console.warn('MCP client not available - not running in Electron environment');
return [];
}
try {
const mcpApi = getDesktopMCPApi()!;
const response = await mcpApi.getTools();
console.log('mcp response from getting tools is', response);
console.log('mcp window desktop api', mcpApi.getTools);
if (response.success && response.tools) {
console.log('Retrieved MCP tools from Electron:', response.tools.length, 'tools');
return response.tools;
} else {
console.warn('Failed to get MCP tools:', response.error);
return [];
}
} catch (error) {
console.error('Error getting MCP tools from Electron:', error);
return [];
}
}
/**
* Execute an MCP tool via Electron main process
*/
async executeTool(
toolName: string,
args: Record<string, any>,
toolCallId?: string
): Promise<any> {
if (!this.isElectron) {
throw new Error('MCP client not available - not running in Electron environment');
}
try {
console.debug('args for tool executed is ', args);
const response = await getDesktopMCPApi()!.executeTool(toolName, args, toolCallId);
if (response.success) {
return response.result;
} else {
throw new Error(response.error || 'Unknown error executing MCP tool');
}
} catch (error) {
console.error(`Error executing MCP tool ${toolName}:`, error);
throw error;
}
}
/**
* Get MCP client status from Electron main process
*/
async getStatus(): Promise<{ isInitialized: boolean; hasClient: boolean }> {
if (!this.isElectron) {
return { isInitialized: false, hasClient: false };
}
try {
return await getDesktopMCPApi()!.getStatus();
} catch (error) {
console.error('Error getting MCP status:', error);
return { isInitialized: false, hasClient: false };
}
}
/**
* Reset/restart MCP client in Electron main process
*/
async resetClient(): Promise<boolean> {
if (!this.isElectron) {
return false;
}
try {
const response = await getDesktopMCPApi()!.resetClient();
return response.success;
} catch (error) {
console.error('Error resetting MCP client:', error);
return false;
}
}
/**
* Get MCP configuration from Electron main process
*/
async getConfig(): Promise<{ success: boolean; config?: any; error?: string }> {
if (!this.isElectron) {
return {
success: false,
error: 'MCP client not available - not running in Electron environment',
};
}
try {
const response = await window.desktopApi!.mcp.getConfig();
return response;
} catch (error) {
console.error('Error getting MCP config:', error);
return { success: false, error: String(error) };
}
}
/**
* Get MCP tools configuration from Electron main process
*/
async getToolsConfig(): Promise<{ success: boolean; config?: any; error?: string }> {
if (!this.isElectron) {
return {
success: false,
error: 'MCP client not available - not running in Electron environment',
};
}
try {
const response = await window.desktopApi!.mcp.getToolsConfig();
return response;
} catch (error) {
console.error('Error getting MCP tools config:', error);
return { success: false, error: String(error) };
}
}
/**
* Update MCP tools configuration in Electron main process
*/
async updateToolsConfig(config: any): Promise<boolean> {
if (!this.isElectron) {
return false;
}
try {
const response = await window.desktopApi!.mcp.updateToolsConfig(config);
console.debug('response from updating mcp tools config is ', response);
return response.success;
} catch (error) {
console.error('Error updating MCP tools config:', error);
return false;
}
}
/**
* Enable or disable a specific MCP tool
*/
async setToolEnabled(serverName: string, toolName: string, enabled: boolean): Promise<boolean> {
if (!this.isElectron) {
return false;
}
try {
const response = await window.desktopApi!.mcp.setToolEnabled(serverName, toolName, enabled);
return response.success;
} catch (error) {
console.error('Error setting tool enabled state:', error);
return false;
}
}
/**
* Get tool statistics for a specific MCP tool
*/
async getToolStats(serverName: string, toolName: string): Promise<any | null> {
if (!this.isElectron) {
return null;
}
try {
const response = await window.desktopApi!.mcp.getToolStats(serverName, toolName);
return response.success ? response.stats : null;
} catch (error) {
console.error('Error getting tool stats:', error);
return null;
}
}
/**
* Parse MCP tool name to extract server and tool components
* Format: "serverName__toolName"
*/
parseToolName(fullToolName: string): { serverName: string; toolName: string } {
const parts = fullToolName.split('__');
if (parts.length >= 2) {
return {
serverName: parts[0],
toolName: parts.slice(1).join('__'),
};
}
return {
serverName: 'default',
toolName: fullToolName,
};
}
/**
* Check if a specific tool is enabled
*/
async isToolEnabled(fullToolName: string): Promise<boolean> {
if (!this.isElectron) {
return true; // Default to enabled if not in Electron
}
try {
const { serverName, toolName } = this.parseToolName(fullToolName);
const toolsConfig = await this.getToolsConfig();
if (!toolsConfig.success || !toolsConfig.config) {
return true; // Default to enabled if config not available
}
const serverConfig = toolsConfig.config[serverName];
if (!serverConfig || !serverConfig[toolName]) {
return true; // Default to enabled for new tools
}
return serverConfig[toolName].enabled;
} catch (error) {
console.error('Error checking tool enabled state:', error);
return true; // Default to enabled on error
}
}
/**
* Get all enabled MCP tools
*/
async getEnabledTools(): Promise<MCPTool[]> {
if (!this.isElectron) {
return [];
}
try {
const toolsConfigResponse = await this.getToolsConfig();
if (!toolsConfigResponse.success || !toolsConfigResponse.config) {
return [];
}
const enabledTools: MCPTool[] = [];
for (const [serverName, serverTools] of Object.entries(toolsConfigResponse.config)) {
for (const [toolName, toolConfig] of Object.entries(serverTools as Record<string, any>)) {
if (toolConfig.enabled !== false) {
enabledTools.push({
name: `${serverName}__${toolName}`,
description: toolConfig.description,
inputSchema: toolConfig.inputSchema,
server: serverName,
});
}
}
}
return enabledTools;
} catch (error) {
console.error('Error getting enabled tools:', error);
return [];
}
}
}
// Export a function that returns tools (compatible with existing interface)
const tools = async function (): Promise<MCPTool[]> {
const client = new ElectronMCPClient();
return (await client.getToolsConfig()).config;
};
// Export both the client class and the tools function for flexibility
export { ElectronMCPClient };
export default tools;