Skip to main content
Glama

mcp-minecraft

bot-connection.ts6.34 kB
import mineflayer from 'mineflayer'; import pathfinderPkg from 'mineflayer-pathfinder'; const { pathfinder, Movements } = pathfinderPkg; import minecraftData from 'minecraft-data'; const SUPPORTED_MINECRAFT_VERSION = '1.21.8'; type ConnectionState = 'connected' | 'connecting' | 'disconnected'; interface BotConfig { host: string; port: number; username: string; } interface ConnectionCallbacks { onLog: (level: string, message: string) => void; onChatMessage: (username: string, message: string) => void; } export class BotConnection { private bot: mineflayer.Bot | null = null; private state: ConnectionState = 'disconnected'; private config: BotConfig; private callbacks: ConnectionCallbacks; private isReconnecting = false; private reconnectTimer: ReturnType<typeof setTimeout> | null = null; private readonly reconnectDelayMs: number; constructor(config: BotConfig, callbacks: ConnectionCallbacks, reconnectDelayMs = 2000) { this.config = config; this.callbacks = callbacks; this.reconnectDelayMs = reconnectDelayMs; } getBot(): mineflayer.Bot | null { return this.bot; } getState(): ConnectionState { return this.state; } getConfig(): BotConfig { return this.config; } isConnected(): boolean { return this.state === 'connected'; } connect(): void { const botOptions = { host: this.config.host, port: this.config.port, username: this.config.username, plugins: { pathfinder }, }; this.bot = mineflayer.createBot(botOptions); this.state = 'connecting'; this.isReconnecting = false; this.registerEventHandlers(this.bot); } private registerEventHandlers(bot: mineflayer.Bot): void { bot.once('spawn', async () => { this.state = 'connected'; this.callbacks.onLog('info', 'Bot spawned in world'); const mcData = minecraftData(bot.version); const defaultMove = new Movements(bot, mcData); bot.pathfinder.setMovements(defaultMove); bot.chat('LLM-powered bot ready to receive instructions!'); this.callbacks.onLog('info', `Bot connected successfully. Username: ${this.config.username}, Server: ${this.config.host}:${this.config.port}`); }); bot.on('chat', (username, message) => { if (username === bot.username) return; this.callbacks.onChatMessage(username, message); }); bot.on('kicked', (reason) => { this.callbacks.onLog('error', `Bot was kicked from server: ${this.formatError(reason)}`); this.state = 'disconnected'; bot.quit(); }); bot.on('error', (err) => { const errorCode = (err as { code?: string }).code || 'Unknown error'; const errorMsg = err instanceof Error ? err.message : String(err); this.callbacks.onLog('error', `Bot error [${errorCode}]: ${errorMsg}`); if (errorCode === 'ECONNREFUSED' || errorCode === 'ETIMEDOUT') { this.state = 'disconnected'; } }); bot.on('login', () => { this.callbacks.onLog('info', 'Bot logged in successfully'); }); bot.on('end', (reason) => { this.callbacks.onLog('info', `Bot disconnected: ${this.formatError(reason)}`); if (this.state === 'connected') { this.state = 'disconnected'; } if (this.bot === bot) { try { bot.removeAllListeners(); this.bot = null; this.callbacks.onLog('info', 'Bot instance cleaned up after disconnect'); } catch (err) { this.callbacks.onLog('warn', `Error cleaning up bot on end event: ${this.formatError(err)}`); } } }); } attemptReconnect(): void { if (this.isReconnecting || this.state === 'connecting') { return; } this.isReconnecting = true; this.state = 'connecting'; this.callbacks.onLog('info', `Attempting to reconnect to Minecraft server in ${this.reconnectDelayMs}ms...`); if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); } this.reconnectTimer = setTimeout(() => { if (this.bot) { try { this.bot.removeAllListeners(); this.bot.quit('Reconnecting...'); this.callbacks.onLog('info', 'Old bot instance cleaned up'); } catch (err) { this.callbacks.onLog('warn', `Error while cleaning up old bot: ${this.formatError(err)}`); } } this.callbacks.onLog('info', 'Creating new bot instance...'); this.connect(); }, this.reconnectDelayMs); } async checkConnectionAndReconnect(): Promise<{ connected: boolean; message?: string }> { const currentState = this.state; if (currentState === 'disconnected') { this.attemptReconnect(); const maxWaitTime = this.reconnectDelayMs + 5000; const pollInterval = 100; const startTime = Date.now(); while (Date.now() - startTime < maxWaitTime) { if (this.state === 'connected') { return { connected: true }; } await new Promise(resolve => setTimeout(resolve, pollInterval)); } const errorMessage = `Cannot connect to Minecraft server at ${this.config.host}:${this.config.port}\n\n` + `Please ensure:\n` + `1. Minecraft server is running on ${this.config.host}:${this.config.port}\n` + `2. Server is accessible from this machine\n` + `3. Server version is compatible (latest supported: ${SUPPORTED_MINECRAFT_VERSION})\n\n` + `For setup instructions, visit: https://github.com/yuniko-software/minecraft-mcp-server`; return { connected: false, message: errorMessage }; } if (currentState === 'connecting') { return { connected: false, message: 'Bot is connecting to the Minecraft server. Please wait a moment and try again.' }; } return { connected: true }; } cleanup(): void { if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); } if (this.bot) { try { this.bot.quit('Server shutting down'); } catch (err) { this.callbacks.onLog('warn', `Error during cleanup: ${this.formatError(err)}`); } } } private formatError(error: unknown): string { if (error instanceof Error) { return error.message; } try { return JSON.stringify(error); } catch { return String(error); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yuniko-software/minecraft-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server