server.ts•7.24 kB
/**
* ByteBot MCP Server
*
* Main MCP server class that orchestrates all ByteBot integrations
* and exposes tools via the Model Context Protocol
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from '@modelcontextprotocol/sdk/types.js';
import { AgentClient } from './clients/agent-client.js';
import { DesktopClient } from './clients/desktop-client.js';
import { WebSocketClient } from './clients/websocket-client.js';
import { EnvironmentConfig, HealthCheckResult } from './types/mcp.js';
import { getAgentTools, handleAgentTool } from './tools/agent-tools.js';
import { getDesktopTools, handleDesktopTool } from './tools/desktop-tools.js';
import {
getHybridTools,
handleHybridTool,
HybridOrchestrator,
} from './tools/hybrid-tools.js';
import { validateEndpoint, validateWebSocketEndpoint } from './utils/error-handler.js';
/**
* ByteBot MCP Server
*/
export class ByteBotMCPServer {
private server: Server;
private agentClient: AgentClient;
private desktopClient: DesktopClient;
private wsClient: WebSocketClient | null = null;
private orchestrator: HybridOrchestrator;
private config: EnvironmentConfig;
private tools: Tool[] = [];
constructor(config: EnvironmentConfig) {
this.config = config;
// Validate endpoints
validateEndpoint(config.agentUrl, 'Agent API');
validateEndpoint(config.desktopUrl, 'Desktop API');
if (config.wsUrl && config.enableWebSocket) {
validateWebSocketEndpoint(config.wsUrl);
}
// Initialize clients
this.agentClient = new AgentClient(config);
this.desktopClient = new DesktopClient(config);
// Initialize WebSocket client if enabled
if (config.enableWebSocket && config.wsUrl) {
this.wsClient = new WebSocketClient(config);
}
// Initialize orchestrator
this.orchestrator = new HybridOrchestrator(
this.agentClient,
this.desktopClient,
this.wsClient,
config
);
// Initialize MCP server
this.server = new Server(
{
name: config.serverName,
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Register all tools
this.registerTools();
// Set up request handlers
this.setupHandlers();
console.log(`[ByteBot MCP] Server initialized: ${config.serverName}`);
}
/**
* Register all available tools
*/
private registerTools(): void {
this.tools = [
...getAgentTools(),
...getDesktopTools(),
...getHybridTools(),
] as Tool[];
console.log(`[ByteBot MCP] Registered ${this.tools.length} tools`);
}
/**
* Set up MCP request handlers
*/
private setupHandlers(): void {
// List tools handler
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: this.tools,
};
});
// Call tool handler
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
console.log(`[ByteBot MCP] Tool called: ${name}`);
try {
// Route to appropriate handler based on tool name prefix
if (name.startsWith('bytebot_create_and_monitor') ||
name.startsWith('bytebot_intervene') ||
name.startsWith('bytebot_execute_workflow') ||
name.startsWith('bytebot_monitor_task')) {
return await handleHybridTool(name, args || {}, this.orchestrator);
} else if (
name.startsWith('bytebot_create_task') ||
name.startsWith('bytebot_list_tasks') ||
name.startsWith('bytebot_get_task') ||
name.startsWith('bytebot_get_in_progress') ||
name.startsWith('bytebot_update_task') ||
name.startsWith('bytebot_delete_task')
) {
return await handleAgentTool(name, args || {}, this.agentClient);
} else {
return await handleDesktopTool(name, args || {}, this.desktopClient);
}
} catch (error) {
console.error(`[ByteBot MCP] Error handling tool ${name}:`, error);
throw error;
}
});
}
/**
* Start the MCP server
*/
async start(): Promise<void> {
console.log('[ByteBot MCP] Starting server...');
// Perform health checks
const health = await this.healthCheck();
if (!health.agentApi) {
console.warn('[ByteBot MCP] Warning: Agent API health check failed');
console.warn(
`[ByteBot MCP] Make sure ByteBot is running at ${this.config.agentUrl}`
);
}
if (!health.desktopApi) {
console.warn('[ByteBot MCP] Warning: Desktop API health check failed');
console.warn(
`[ByteBot MCP] Make sure ByteBot is running at ${this.config.desktopUrl}`
);
}
// Connect WebSocket if enabled
if (this.wsClient) {
try {
await this.wsClient.connect();
console.log('[ByteBot MCP] WebSocket connected');
// Subscribe to all task updates
this.wsClient.subscribeToAllTasks();
} catch (error) {
console.warn(
'[ByteBot MCP] WebSocket connection failed. Falling back to polling:',
error
);
}
}
// Start stdio transport
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.log('[ByteBot MCP] Server started on stdio transport');
console.log('[ByteBot MCP] Ready to accept MCP requests');
}
/**
* Stop the MCP server
*/
async stop(): Promise<void> {
console.log('[ByteBot MCP] Stopping server...');
// Disconnect WebSocket
if (this.wsClient) {
this.wsClient.disconnect();
}
// Clear caches
this.agentClient.clearCache();
await this.server.close();
console.log('[ByteBot MCP] Server stopped');
}
/**
* Health check for all ByteBot endpoints
*/
async healthCheck(): Promise<HealthCheckResult> {
const result: HealthCheckResult = {
agentApi: false,
desktopApi: false,
timestamp: new Date().toISOString(),
errors: [],
};
// Check Agent API
try {
result.agentApi = await this.agentClient.healthCheck();
} catch (error) {
result.errors.push(
`Agent API: ${error instanceof Error ? error.message : String(error)}`
);
}
// Check Desktop API
try {
result.desktopApi = await this.desktopClient.healthCheck();
} catch (error) {
result.errors.push(
`Desktop API: ${error instanceof Error ? error.message : String(error)}`
);
}
// Check WebSocket if enabled
if (this.wsClient) {
result.webSocket = this.wsClient.isConnected();
}
return result;
}
/**
* Get server statistics
*/
getStats() {
return {
serverName: this.config.serverName,
toolsRegistered: this.tools.length,
agentApiUrl: this.config.agentUrl,
desktopApiUrl: this.config.desktopUrl,
webSocketEnabled: this.config.enableWebSocket,
webSocketConnected: this.wsClient?.isConnected() || false,
};
}
}