#
tokens: 13720/50000 10/10 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── LICENSE
├── package.json
├── public
│   ├── focus.png
│   ├── overview.jpeg
│   └── screenshot.png
├── README.md
├── src
│   ├── Application.ts
│   ├── index.ts
│   ├── MCPHandler.ts
│   ├── ProtocolHandler.ts
│   ├── ServerManager.ts
│   └── types
│       └── config.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Build output
 2 | /dist
 3 | 
 4 | # Dependencies
 5 | /node_modules
 6 | /libraries
 7 | 
 8 | # Server files
 9 | /versions
10 | /world
11 | /logs
12 | /minecraft-server
13 | 
14 | # Configuration files
15 | banned-ips.json
16 | banned-players.json
17 | ops.json
18 | package-lock.json
19 | server.properties
20 | usercache.json
21 | whitelist.json
22 | eula.txt
23 | 
24 | # IDE and system files
25 | .DS_Store
26 | .env
27 | *.log
28 | 
29 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Minecraft MCP Integration
  2 | 
  3 | A Model Context Protocol (MCP) integration for Minecraft that enables AI assistants to interact with a Minecraft server. This integration allows AI models to observe and interact with the Minecraft world through a bot.
  4 | 
  5 | ![Screenshot](/public/screenshot.png?quality=medium)
  6 | 
  7 | ## Prerequisites
  8 | 
  9 | 1. Minecraft Launcher
 10 | 2. Node.js 18 or higher
 11 | 3. Claude Desktop App
 12 | 4. Java 21.0.5 (recommended)
 13 | 
 14 | > ⚠️ Note: Currently only tested on macOS/Linux. Windows compatibility is not guaranteed.
 15 | 
 16 | ## Important Note
 17 | 
 18 | 1. **Use the F3+P Shortcut**:
 19 | Press F3 + P together. This toggles the "Pause on Lost Focus" feature. Once turned off, you can switch to claude desktop and Minecraft will continue running without pausing.
 20 | 
 21 | ![Focus Settings](/public/focus.png)
 22 | 
 23 | 2. **Connection Issues on Claude Restart**:
 24 | If you restart Claude while the Minecraft server is running, you may experience MCP connection issues on the next claude launch due to lingering java process. See [Troubleshooting: MCP Connection Failed](#common-issues) for resolution steps.
 25 | 
 26 | ## Installation Steps
 27 | 
 28 | 1. **Download and Setup Minecraft Server**
 29 |    - Download Minecraft server v1.21 from [mcversions.net/1.21](https://mcversions.net/download/1.21)
 30 |    - Install Java 21.0.5 if not already installed (other versions are untested)
 31 |    - Create a dedicated directory (e.g., `~/minecraft-server/`)
 32 |    - Place the downloaded `server.jar` file in this directory
 33 |    - Note down the absolute path to your `server.jar` file
 34 | 
 35 | 2. **Install and Configure MCP Integration**
 36 |    
 37 |    Quick Install (Recommended):
 38 |    ```bash
 39 |    npx -y @smithery/cli install mcp-minecraft --client claude
 40 |    ```
 41 |    Follow the CLI prompts to complete the setup.
 42 | 
 43 |    Or Manual Setup:
 44 |    - Navigate to `~/Library/Application Support/Claude/claude_desktop_config.json`
 45 |    - Add the MCP server configuration:   
 46 |    ```json
 47 |    {
 48 |      "mcpServers": {
 49 |        "mcp-minecraft": {
 50 |          "command": "npx",
 51 |          "args": [
 52 |            "-y",
 53 |            "mcp-minecraft@latest",
 54 |            "--server-jar",
 55 |            "/absolute/path/to/minecraft-server/server.jar"
 56 |          ]
 57 |        }
 58 |      }
 59 |    }   
 60 |    ```
 61 |    > ⚠️ Replace `/absolute/path/to/minecraft-server/server.jar` with your actual server.jar path
 62 | 
 63 | 4. **Launch Claude Desktop**
 64 |    - Start Claude Desktop after completing the configuration
 65 | 
 66 | 5. **Connect to Server**
 67 |    - Open Minecraft Launcher
 68 |    - Install and launch Minecraft Java Edition **v1.21**
 69 |    - Click "Play" and Select "Multiplayer"
 70 |    - Click "Add Server"
 71 |    - Enter server details:
 72 |      - Server Name: `Minecraft Server`
 73 |      - Server Address: `localhost:25565`
 74 |    - Click "Done"
 75 | 
 76 | ## Features
 77 | 
 78 | ### Resources
 79 | The integration exposes these MCP resources:
 80 | 
 81 | - `minecraft://bot/location` - Current bot position in the world
 82 | - `minecraft://bot/status` - Bot connection status
 83 | 
 84 | ### Tools
 85 | Available MCP tools:
 86 | 
 87 | - `chat` - Send chat messages to the server
 88 | - `jump` - Make the bot jump
 89 | - `moveForward` - Make the bot move forward
 90 | - `moveBack` - Make the bot move backward
 91 | - `turnLeft` - Make the bot turn left
 92 | - `turnRight` - Make the bot turn right
 93 | - `placeBlock` - Place a block at specified coordinates
 94 | - `digBlock` - Break a block at specified coordinates
 95 | - `getBlockInfo` - Get information about a block at specified coordinates
 96 | - `selectSlot` - Select a hotbar slot (0-8)
 97 | - `getInventory` - Get contents of bot's inventory
 98 | - `equipItem` - Equip an item by name to specified destination
 99 | - `getStatus` - Get bot's current status (health, food, position, etc.)
100 | - `getNearbyEntities` - Get list of nearby entities within range
101 | - `attack` - Attack a nearby entity by name
102 | - `useItem` - Use/activate the currently held item
103 | - `stopUsingItem` - Stop using/deactivate the current item
104 | - `lookAt` - Make the bot look at specific coordinates
105 | - `followPlayer` - Follow a specific player
106 | - `stopFollowing` - Stop following current target
107 | - `goToPosition` - Navigate to specific coordinates
108 | 
109 | ## Technical Details
110 | 
111 | - Server runs in offline mode for local development
112 | - Default memory allocation: 2GB
113 | - Default port: 25565
114 | - Bot username: MCPBot
115 | 
116 | ## Troubleshooting
117 | 
118 | ### Common Issues
119 | 
120 | 1. **MCP Connection Failed**
121 |    - Look for lingering Java processes
122 |    - Terminate them manually:
123 |       - Windows: Use Task Manager (untested)
124 |       - Mac/Linux: 
125 |          - Go to 'Activity Monitor' and 'Force Quit' java
126 |    - Restart computer if process termination fails
127 |    - Note: Latest version should auto-resolve these issues
128 | 
129 | 2. **Server Won't Start**
130 |    - Verify Java is installed
131 |    - Check server.jar path is correct
132 |    - Ensure port 25565 is available
133 | 
134 | 3. **Can't Connect to Server**
135 |    - Verify server is running (check logs)
136 |    - Confirm you're using "localhost" as server address
137 |    - Check firewall settings
138 | 
139 | ### Logs Location
140 | - Minecraft Server logs: Check the minecraft-server directory
141 | - Claude Desktop logs: `~/Library/Logs/Claude/mcp*.log`
142 | 
143 | ## Contributing
144 | 
145 | Contributions, big or small, are welcome!
146 | 
147 | ## License
148 | 
149 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
150 | 
```

--------------------------------------------------------------------------------
/src/types/config.ts:
--------------------------------------------------------------------------------

```typescript
1 | export interface MinecraftServerConfig {
2 |   maxPlayers: number;
3 |   port: number;
4 |   serverJarPath: string;
5 |   memoryAllocation: string;
6 |   username: string;
7 |   version: string;
8 | } 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "NodeNext",
 5 |     "moduleResolution": "NodeNext",
 6 |     "outDir": "dist",
 7 |     "rootDir": "src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "resolveJsonModule": true
13 |   },
14 |   "ts-node": {
15 |     "esm": true,
16 |     "experimentalSpecifierResolution": "node"
17 |   },
18 |   "include": ["src/**/*.ts"],
19 |   "exclude": ["node_modules"]
20 | }
21 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "mcp-minecraft",
 3 |   "version": "1.0.34",
 4 |   "type": "module",
 5 |   "main": "index.js",
 6 |   "scripts": {
 7 |     "test": "echo \"Error: no test specified\" && exit 1",
 8 |     "start": "node --loader ts-node/esm src/index.ts",
 9 |     "dev": "ts-node-dev --respawn --transpile-only --esm src/index.ts",
10 |     "build": "tsc"
11 |   },
12 |   "author": "",
13 |   "license": "ISC",
14 |   "description": "",
15 |   "dependencies": {
16 |     "@modelcontextprotocol/sdk": "latest",
17 |     "@types/node": "^22.10.2",
18 |     "minecraft-protocol": "^1.51.0",
19 |     "mineflayer": "^4.23.0",
20 |     "mineflayer-pathfinder": "^2.4.5",
21 |     "ts-node": "^10.9.2",
22 |     "typescript": "^5.7.2",
23 |     "yargs": "^17.7.2"
24 |   },
25 |   "devDependencies": {
26 |     "@types/yargs": "^17.0.33",
27 |     "ts-node-dev": "^2.0.0"
28 |   },
29 |   "bin": {
30 |     "minecraft-mcp": "./dist/index.js"
31 |   }
32 | }
33 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | 
 3 | import { Application } from './Application.js';
 4 | import yargs from 'yargs';
 5 | import { hideBin } from 'yargs/helpers';
 6 | import * as fs from 'fs';
 7 | 
 8 | const argv = yargs(hideBin(process.argv))
 9 |   .option('server-jar', {
10 |     alias: 'j',
11 |     type: 'string',
12 |     description: 'Absolute path to the Minecraft server JAR file',
13 |     demandOption: true
14 |   })
15 |   .scriptName('minecraft-mcp')
16 |   .help()
17 |   .parseSync();
18 | 
19 | if (!fs.existsSync(argv.serverJar)) {
20 |   process.exit(1);
21 | }
22 | 
23 | const app = new Application({
24 |   serverJarPath: argv.serverJar
25 | });
26 | 
27 | const cleanup = async () => {
28 |   try {
29 |     await app.stop();
30 |     process.exit(0);
31 |   } catch (error) {
32 |     process.exit(1);
33 |   }
34 | };
35 | 
36 | process.on('SIGINT', cleanup);
37 | process.on('SIGTERM', cleanup);
38 | 
39 | process.on('uncaughtException', async (error) => {
40 |   await cleanup();
41 | });
42 | 
43 | process.on('unhandledRejection', async (error) => {
44 |   await cleanup();
45 | });
46 | 
47 | app.start().catch(() => {
48 |   process.exit(1);
49 | });
50 | 
```

--------------------------------------------------------------------------------
/src/Application.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  2 | import { ServerManager } from "./ServerManager.js";
  3 | import { ProtocolHandler } from "./ProtocolHandler.js";
  4 | import { MCPHandler } from "./MCPHandler.js";
  5 | import path from 'path';
  6 | 
  7 | interface ApplicationConfig {
  8 |   serverJarPath: string;
  9 | }
 10 | 
 11 | export class Application {
 12 |   private serverManager: ServerManager;
 13 |   private protocolHandler: ProtocolHandler;
 14 |   private mcpHandler: MCPHandler;
 15 |   private transport: StdioServerTransport | null = null;
 16 | 
 17 |   constructor(config: ApplicationConfig) {
 18 |     const serverPath = path.resolve(config.serverJarPath);
 19 |     
 20 |     // Initialize with config from CLI
 21 |     this.serverManager = new ServerManager({
 22 |       maxPlayers: 10,
 23 |       port: 25565,
 24 |       serverJarPath: serverPath,
 25 |       memoryAllocation: '2G',
 26 |       username: 'MCPBot',
 27 |       version: '1.21'
 28 |     });
 29 | 
 30 |     this.protocolHandler = new ProtocolHandler({
 31 |       host: 'localhost',
 32 |       port: 25565,
 33 |       username: 'MCPBot',
 34 |       version: '1.21'
 35 |     });
 36 | 
 37 |     this.mcpHandler = new MCPHandler(this.protocolHandler);
 38 | 
 39 |     this.setupEventHandlers();
 40 |   }
 41 | 
 42 |   private setupEventHandlers(): void {
 43 |     this.serverManager.on('log', (message) => {
 44 |     });
 45 | 
 46 |     this.serverManager.on('error', (error) => {
 47 |     });
 48 | 
 49 |     this.protocolHandler.on('chat', ({ username, message }) => {
 50 |     });
 51 | 
 52 |     this.protocolHandler.on('error', (error) => {
 53 |     });
 54 | 
 55 |     process.on('SIGINT', async () => {
 56 |       await this.shutdown();
 57 |       process.exit(0);
 58 |     });
 59 |   }
 60 | 
 61 |   public async start(): Promise<void> {
 62 |     try {
 63 |       // Start MCP server first - use only stdout for MCP communication
 64 |       this.transport = new StdioServerTransport();
 65 |       await this.mcpHandler.getServer().connect(this.transport);
 66 | 
 67 |       // Start Minecraft server
 68 |       await this.serverManager.start();
 69 | 
 70 |       // Wait a bit for the server to initialize
 71 |       await new Promise(resolve => setTimeout(resolve, 5000));
 72 | 
 73 |       // Connect bot
 74 |       await this.protocolHandler.connect();
 75 | 
 76 |     } catch (error) {
 77 |       await this.shutdown();
 78 |       process.exit(1);
 79 |     }
 80 |   }
 81 | 
 82 |   public async shutdown(): Promise<void> {
 83 |     if (this.mcpHandler && this.transport) {
 84 |       this.transport = null;
 85 |     }
 86 |     
 87 |     await this.protocolHandler.disconnect();
 88 |     await this.serverManager.stop();
 89 |   }
 90 | 
 91 |   async stop(): Promise<void> {
 92 |     try {
 93 |       // Disconnect MCP server
 94 |       if (this.mcpHandler && this.transport) {
 95 |         await this.mcpHandler.getServer().close();
 96 |         this.transport = null;
 97 |       }
 98 | 
 99 |       // Disconnect bot
100 |       await this.protocolHandler.disconnect();
101 | 
102 |       // Stop Minecraft server
103 |       await this.serverManager.stop();
104 | 
105 |     } catch (error) {
106 |       throw error;
107 |     }
108 |   }
109 | } 
```

--------------------------------------------------------------------------------
/src/ServerManager.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { spawn, ChildProcess } from 'child_process';
  2 | import { EventEmitter } from 'events';
  3 | import { MinecraftServerConfig } from './types/config.js';
  4 | import * as fs from 'fs';
  5 | import path from 'path';
  6 | import * as os from 'os';
  7 | 
  8 | export class ServerManager extends EventEmitter {
  9 |   private process: ChildProcess | null = null;
 10 |   private config: MinecraftServerConfig;
 11 |   private isRunning: boolean = false;
 12 | 
 13 |   constructor(config: MinecraftServerConfig) {
 14 |     super();
 15 |     this.config = this.validateConfig(config);
 16 |     
 17 |     process.on('exit', () => {
 18 |       this.killProcess();
 19 |     });
 20 | 
 21 |     process.on('SIGTERM', () => {
 22 |       this.killProcess();
 23 |     });
 24 | 
 25 |     process.on('SIGINT', () => {
 26 |       this.killProcess();
 27 |     });
 28 |   }
 29 | 
 30 |   private killProcess(): void {
 31 |     if (this.process) {
 32 |       try {
 33 |         process.kill(-this.process.pid!, 'SIGKILL');
 34 |       } catch (error) {
 35 |         // Ignore errors during force kill
 36 |       }
 37 |       this.process = null;
 38 |       this.isRunning = false;
 39 |     }
 40 |   }
 41 | 
 42 |   private validateConfig(config: MinecraftServerConfig): MinecraftServerConfig {
 43 |     if (!config.serverJarPath) {
 44 |       throw new Error('Server JAR path is required');
 45 |     }
 46 |     if (!config.port || config.port < 1 || config.port > 65535) {
 47 |       throw new Error('Invalid port number');
 48 |     }
 49 |     return {
 50 |       maxPlayers: config.maxPlayers || 20,
 51 |       port: config.port,
 52 |       serverJarPath: config.serverJarPath,
 53 |       memoryAllocation: config.memoryAllocation || '2G',
 54 |       username: config.username || 'MCPBot',
 55 |       version: config.version || '1.21'
 56 |     };
 57 |   }
 58 | 
 59 |   private ensureEulaAccepted(): void {
 60 |     // Get the directory containing the server JAR
 61 |     const serverDir = path.dirname(this.config.serverJarPath);
 62 |     const eulaPath = path.join(serverDir, 'eula.txt');
 63 | 
 64 |     // Create or update eula.txt
 65 |     fs.writeFileSync(eulaPath, 'eula=true', 'utf8');
 66 |   }
 67 | 
 68 |   private ensureServerProperties(): void {
 69 |     const serverDir = path.dirname(this.config.serverJarPath);
 70 |     const propsPath = path.join(serverDir, 'server.properties');
 71 | 
 72 |     let properties = '';
 73 |     if (fs.existsSync(propsPath)) {
 74 |       properties = fs.readFileSync(propsPath, 'utf8');
 75 |     }
 76 | 
 77 |     // Define our server properties for a simple plains world
 78 |     const serverProperties = {
 79 |       'online-mode': 'false',
 80 |       'level-type': 'flat',
 81 |       'spawn-protection': '0',
 82 |       'difficulty': 'peaceful',          // No hostile mobs
 83 |       'spawn-monsters': 'false',         // Disable monster spawning
 84 |       'spawn-animals': 'true',           // Enable animal spawning
 85 |       'spawn-npcs': 'false',             // Disable villagers
 86 |       'generate-structures': 'false',     // Disable structures (villages, temples, etc)
 87 |       'allow-nether': 'false',           // Disable nether
 88 |       'gamemode': 'creative',            // Set creative mode for easier building
 89 |       'do-daylight-cycle': 'false',
 90 |       'max-players': this.config.maxPlayers.toString(),
 91 |       'server-port': this.config.port.toString(),
 92 |       'motd': 'Peaceful Plains Server'
 93 |     };
 94 | 
 95 |     // Update or create each property
 96 |     for (const [key, value] of Object.entries(serverProperties)) {
 97 |       const regex = new RegExp(`^${key}=.*$`, 'm');
 98 |       if (properties.match(regex)) {
 99 |         properties = properties.replace(regex, `${key}=${value}`);
100 |       } else {
101 |         properties += `\n${key}=${value}`;
102 |       }
103 |     }
104 | 
105 |     fs.writeFileSync(propsPath, properties.trim(), 'utf8');
106 |   }
107 | 
108 |   private normalizePath(p: string): string {
109 |     return path.normalize(p).toLowerCase();
110 |   }
111 | 
112 |   private expandHome(filepath: string): string {
113 |     if (filepath.startsWith("~/") || filepath === "~") {
114 |       return path.join(os.homedir(), filepath.slice(1));
115 |     }
116 |     return filepath;
117 |   }
118 | 
119 |   private validateServerPath(): string {
120 |     const expandedPath = this.expandHome(this.config.serverJarPath);
121 |     const absolutePath = path.isAbsolute(expandedPath) 
122 |       ? path.resolve(expandedPath)
123 |       : path.resolve(process.cwd(), expandedPath);
124 |     
125 |     if (!fs.existsSync(absolutePath)) {
126 |       throw new Error(`Server JAR not found at path: ${absolutePath}`);
127 |     }
128 |     
129 |     return absolutePath;
130 |   }
131 | 
132 |   public async start(): Promise<void> {
133 |     if (this.isRunning) {
134 |       throw new Error('Server is already running');
135 |     }
136 | 
137 |     return new Promise((resolve, reject) => {
138 |       try {
139 |         const serverJarPath = this.validateServerPath();
140 |         const serverDir = path.dirname(serverJarPath);
141 |         
142 |         this.ensureEulaAccepted();
143 |         this.ensureServerProperties();
144 | 
145 |         this.process = spawn('java', [
146 |           `-Xmx${this.config.memoryAllocation}`,
147 |           `-Xms${this.config.memoryAllocation}`,
148 |           '-jar',
149 |           serverJarPath,
150 |           'nogui'
151 |         ], {
152 |           cwd: serverDir,
153 |           stdio: ['pipe', 'pipe', 'pipe'],
154 |           detached: true,
155 |           ...(process.platform !== 'win32' && { pid: true })
156 |         });
157 | 
158 |         const timeout = setTimeout(() => {
159 |           reject(new Error('Server startup timed out'));
160 |           this.stop();
161 |         }, 60000);
162 | 
163 |         this.process.stdout?.on('data', (data: Buffer) => {
164 |           const message = data.toString();
165 |           
166 |           if (message.includes('Done')) {
167 |             clearTimeout(timeout);
168 |             this.isRunning = true;
169 |             resolve();
170 |           }
171 |         });
172 | 
173 |         this.process.stderr?.on('data', (data: Buffer) => {
174 |           const error = data.toString();
175 |           if (error.includes('Error')) {
176 |             reject(new Error(error));
177 |           }
178 |         });
179 | 
180 |         this.process.on('close', (code) => {
181 |           this.isRunning = false;
182 |         });
183 | 
184 |         this.process.on('error', (err) => {
185 |           this.isRunning = false;
186 |           reject(err);
187 |         });
188 | 
189 |       } catch (error) {
190 |         reject(error);
191 |       }
192 |     });
193 |   }
194 | 
195 |   public async stop(): Promise<void> {
196 |     if (!this.isRunning || !this.process) {
197 |       return;
198 |     }
199 | 
200 |     return new Promise((resolve) => {
201 |       const forceKillTimeout = setTimeout(() => {
202 |         this.killProcess();
203 |         resolve();
204 |       }, 10000);
205 | 
206 |       this.process?.once('close', () => {
207 |         clearTimeout(forceKillTimeout);
208 |         this.isRunning = false;
209 |         this.process = null;
210 |         resolve();
211 |       });
212 |       
213 |       if (this.process?.stdin) {
214 |         this.process.stdin.write('stop\n');
215 |       } else {
216 |         this.killProcess();
217 |         resolve();
218 |       }
219 |     });
220 |   }
221 | 
222 |   public isServerRunning(): boolean {
223 |     return this.isRunning;
224 |   }
225 | } 
```

--------------------------------------------------------------------------------
/src/ProtocolHandler.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as mineflayer from 'mineflayer';
  2 | import { EventEmitter } from 'events';
  3 | import { Vec3 } from 'vec3';
  4 | import pathfinderPkg from 'mineflayer-pathfinder';
  5 | const { pathfinder, Movements, goals } = pathfinderPkg;
  6 | 
  7 | export interface BotConfig {
  8 |   host: string;
  9 |   port: number;
 10 |   username: string;
 11 |   version: string;
 12 | }
 13 | 
 14 | export class ProtocolHandler extends EventEmitter {
 15 |   private bot: mineflayer.Bot | null = null;
 16 |   private config: BotConfig;
 17 | 
 18 |   constructor(config: BotConfig) {
 19 |     super();
 20 |     this.config = config;
 21 |   }
 22 | 
 23 |   public async connect(): Promise<void> {
 24 |     if (this.bot) {
 25 |       throw new Error('Bot is already connected');
 26 |     }
 27 | 
 28 |     return new Promise((resolve, reject) => {
 29 |       try {
 30 |         this.bot = mineflayer.createBot({
 31 |           host: this.config.host,
 32 |           port: this.config.port,
 33 |           username: this.config.username,
 34 |           version: this.config.version
 35 |         });
 36 | 
 37 |         this.bot.once('spawn', () => {
 38 |           this.bot?.loadPlugin(pathfinder);
 39 |           
 40 |           if (this.bot?.pathfinder) {
 41 |             this.bot.pathfinder.setMovements(new Movements(this.bot));
 42 |           }
 43 | 
 44 |           this.setupEventHandlers();
 45 |           this.emit('connected');
 46 |           resolve();
 47 |         });
 48 | 
 49 |         this.bot.on('error', (error) => {
 50 |           this.emit('error', error);
 51 |           reject(error);
 52 |         });
 53 | 
 54 |       } catch (error) {
 55 |         reject(error);
 56 |       }
 57 |     });
 58 |   }
 59 | 
 60 |   private setupEventHandlers(): void {
 61 |     if (!this.bot) return;
 62 | 
 63 |     this.bot.on('chat', (username, message) => {
 64 |       this.emit('chat', { username, message });
 65 |     });
 66 | 
 67 |     this.bot.on('kicked', (reason) => {
 68 |       this.emit('kicked', reason);
 69 |     });
 70 | 
 71 |     this.bot.on('error', (error) => {
 72 |       this.emit('error', error);
 73 |     });
 74 |   }
 75 | 
 76 |   public async sendChat(message: string): Promise<void> {
 77 |     if (!this.bot) throw new Error('Bot not connected');
 78 |     await this.bot.chat(message);
 79 |   }
 80 | 
 81 |   public async jump(): Promise<void> {
 82 |     if (!this.bot) throw new Error('Bot not connected');
 83 |     this.bot.setControlState('jump', true);
 84 |     setTimeout(() => {
 85 |       if (this.bot) this.bot.setControlState('jump', false);
 86 |     }, 500);
 87 |   }
 88 | 
 89 |   public getPosition(): Vec3 | null {
 90 |     if (!this.bot || !this.bot.entity) return null;
 91 |     return this.bot.entity.position;
 92 |   }
 93 | 
 94 |   public async disconnect(): Promise<void> {
 95 |     if (!this.bot) return;
 96 |     
 97 |     return new Promise((resolve) => {
 98 |       const bot = this.bot;
 99 |       if (!bot) {
100 |         resolve();
101 |         return;
102 |       }
103 |       
104 |       bot.removeAllListeners();
105 |       
106 |       bot.once('end', () => {
107 |         this.bot = null;
108 |         
109 |         setTimeout(() => {
110 |           process.exit(0);
111 |         }, 1000);
112 |         
113 |         resolve();
114 |       });
115 |       
116 |       bot.end();
117 |     });
118 |   }
119 | 
120 |   public isConnected(): boolean {
121 |     return this.bot !== null;
122 |   }
123 | 
124 |   public async moveForward(): Promise<void> {
125 |     if (!this.bot) throw new Error('Bot not connected');
126 |     this.bot.setControlState('forward', true);
127 |     await new Promise(resolve => setTimeout(resolve, 1000));
128 |     this.bot.setControlState('forward', false);
129 |   }
130 | 
131 |   public async moveBack(): Promise<void> {
132 |     if (!this.bot) throw new Error('Bot not connected');
133 |     this.bot.setControlState('back', true);
134 |     await new Promise(resolve => setTimeout(resolve, 1000));
135 |     this.bot.setControlState('back', false);
136 |   }
137 | 
138 |   public async turnLeft(): Promise<void> {
139 |     if (!this.bot) throw new Error('Bot not connected');
140 |     this.bot.setControlState('left', true);
141 |     await new Promise(resolve => setTimeout(resolve, 500));
142 |     this.bot.setControlState('left', false);
143 |   }
144 | 
145 |   public async turnRight(): Promise<void> {
146 |     if (!this.bot) throw new Error('Bot not connected');
147 |     this.bot.setControlState('right', true);
148 |     await new Promise(resolve => setTimeout(resolve, 500));
149 |     this.bot.setControlState('right', false);
150 |   }
151 | 
152 |   public async placeBlock(x: number, y: number, z: number): Promise<void> {
153 |     if (!this.bot) throw new Error('Bot not connected');
154 |     
155 |     try {
156 |       const targetPos = new Vec3(x, y, z);
157 |       const faceVector = new Vec3(0, 1, 0);
158 |       
159 |       const referenceBlock = await this.bot.blockAt(targetPos);
160 |       if (!referenceBlock) throw new Error('No reference block found');
161 |       
162 |       await this.bot.placeBlock(referenceBlock, faceVector);
163 |     } catch (error) {
164 |       throw new Error(`Failed to place block: ${error}`);
165 |     }
166 |   }
167 | 
168 |   public async digBlock(x: number, y: number, z: number): Promise<void> {
169 |     if (!this.bot) throw new Error('Bot not connected');
170 |     
171 |     try {
172 |       const targetPos = new Vec3(x, y, z);
173 |       const block = await this.bot.blockAt(targetPos);
174 |       
175 |       if (!block) throw new Error('No block at target position');
176 |       if (block.name === 'air') throw new Error('Cannot dig air');
177 |       
178 |       await this.bot.dig(block);
179 |     } catch (error) {
180 |       throw new Error(`Failed to dig block: ${error}`);
181 |     }
182 |   }
183 | 
184 |   public async getBlockInfo(x: number, y: number, z: number): Promise<any> {
185 |     if (!this.bot) throw new Error('Bot not connected');
186 |     
187 |     try {
188 |       const targetPos = new Vec3(x, y, z);
189 |       const block = await this.bot.blockAt(targetPos);
190 |       
191 |       if (!block) throw new Error('No block at target position');
192 |       
193 |       return {
194 |         name: block.name,
195 |         type: block.type,
196 |         position: {
197 |           x: block.position.x,
198 |           y: block.position.y,
199 |           z: block.position.z
200 |         },
201 |         hardness: block.hardness
202 |       };
203 |     } catch (error) {
204 |       throw new Error(`Failed to get block info: ${error}`);
205 |     }
206 |   }
207 | 
208 |   public async selectSlot(slot: number): Promise<void> {
209 |     if (!this.bot) throw new Error('Bot not connected');
210 |     if (slot < 0 || slot > 8) throw new Error('Slot must be between 0 and 8');
211 |     
212 |     try {
213 |       await this.bot.setQuickBarSlot(slot);
214 |     } catch (error) {
215 |       throw new Error(`Failed to select slot: ${error}`);
216 |     }
217 |   }
218 | 
219 |   public async getInventory(): Promise<any> {
220 |     if (!this.bot) throw new Error('Bot not connected');
221 |     
222 |     const items = this.bot.inventory.items();
223 |     return items.map(item => ({
224 |       name: item.name,
225 |       count: item.count,
226 |       slot: item.slot,
227 |       displayName: item.displayName
228 |     }));
229 |   }
230 | 
231 |   public async equipItem(itemName: string, destination?: string): Promise<void> {
232 |     if (!this.bot) throw new Error('Bot not connected');
233 |     
234 |     try {
235 |         const item = this.bot.inventory.items().find(item => item.name.includes(itemName));
236 |         if (!item) throw new Error(`Item ${itemName} not found in inventory`);
237 |         
238 |         const equipDestination: mineflayer.EquipmentDestination | null = destination as mineflayer.EquipmentDestination || null;
239 |         await this.bot.equip(item, equipDestination);
240 |     } catch (error) {
241 |         throw new Error(`Failed to equip item: ${error}`);
242 |     }
243 |   }
244 | 
245 |   public async getStatus(): Promise<any> {
246 |     if (!this.bot) throw new Error('Bot not connected');
247 |     
248 |     return {
249 |         health: this.bot.health,
250 |         food: this.bot.food,
251 |         gameMode: this.bot.game?.gameMode ?? 'unknown',
252 |         position: this.getPosition(),
253 |         isRaining: this.bot.isRaining,
254 |         time: {
255 |             timeOfDay: this.bot.time?.timeOfDay ?? 0,
256 |             day: this.bot.time?.day ?? 0
257 |         }
258 |     };
259 |   }
260 | 
261 |   public async getNearbyEntities(range: number = 10): Promise<any[]> {
262 |     if (!this.bot) throw new Error('Bot not connected');
263 |     
264 |     return Object.values(this.bot.entities)
265 |         .filter(entity => {
266 |             if (!entity.position || !this.bot?.entity?.position) return false;
267 |             return entity.position.distanceTo(this.bot.entity.position) <= range;
268 |         })
269 |         .map(entity => ({
270 |             name: entity.name,
271 |             type: entity.type,
272 |             position: entity.position,
273 |             distance: entity.position && this.bot?.entity?.position 
274 |                 ? entity.position.distanceTo(this.bot.entity.position) 
275 |                 : null // Handle the case where position might be null
276 |         }));
277 |   }
278 | 
279 |   public async attack(entityName: string): Promise<void> {
280 |     if (!this.bot) throw new Error('Bot not connected');
281 |     
282 |     try {
283 |       const entity = Object.values(this.bot.entities)
284 |         .find(e => e.name === entityName && 
285 |           e.position.distanceTo(this.bot!.entity.position) <= 4);
286 |       
287 |       if (!entity) throw new Error('Entity not found or too far');
288 |       await this.bot.attack(entity);
289 |     } catch (error) {
290 |       throw new Error(`Failed to attack: ${error}`);
291 |     }
292 |   }
293 | 
294 |   public async useItem(hand: 'right' | 'left' = 'right'): Promise<void> {
295 |     if (!this.bot) throw new Error('Bot not connected');
296 |     
297 |     try {
298 |       await this.bot.activateItem(hand === 'right');
299 |     } catch (error) {
300 |       throw new Error(`Failed to use item: ${error}`);
301 |     }
302 |   }
303 | 
304 |   public async stopUsingItem(): Promise<void> {
305 |     if (!this.bot) throw new Error('Bot not connected');
306 |     
307 |     try {
308 |       await this.bot.deactivateItem();
309 |     } catch (error) {
310 |       throw new Error(`Failed to stop using item: ${error}`);
311 |     }
312 |   }
313 | 
314 |   public async lookAt(x: number, y: number, z: number): Promise<void> {
315 |     if (!this.bot) throw new Error('Bot not connected');
316 |     
317 |     try {
318 |       await this.bot.lookAt(new Vec3(x, y, z));
319 |     } catch (error) {
320 |       throw new Error(`Failed to look at position: ${error}`);
321 |     }
322 |   }
323 | 
324 |   public async followPlayer(playerName: string): Promise<void> {
325 |     if (!this.bot) throw new Error('Bot not connected');
326 |     
327 |     try {
328 |         const player = this.bot.players[playerName]?.entity;
329 |         if (!player) throw new Error('Player not found');
330 | 
331 |         // Follow at 2 blocks distance
332 |         await this.bot.pathfinder.goto(
333 |             new goals.GoalFollow(player, 2)
334 |         );
335 |     } catch (error) {
336 |         throw new Error(`Failed to follow player: ${error}`);
337 |     }
338 |   }
339 | 
340 |   public async stopFollowing(): Promise<void> {
341 |     if (!this.bot) throw new Error('Bot not connected');
342 |     this.bot.pathfinder.stop();
343 |   }
344 | 
345 |   public async goToPosition(x: number, y: number, z: number): Promise<void> {
346 |     if (!this.bot) throw new Error('Bot not connected');
347 |     
348 |     try {
349 |         await this.bot.pathfinder.goto(
350 |             new goals.GoalBlock(x, y, z)
351 |         );
352 |     } catch (error) {
353 |         throw new Error(`Failed to go to position: ${error}`);
354 |     }
355 |   }
356 | } 
```

--------------------------------------------------------------------------------
/src/MCPHandler.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  2 | import { 
  3 |   ListResourcesRequestSchema, 
  4 |   ReadResourceRequestSchema,
  5 |   ListToolsRequestSchema,
  6 |   CallToolRequestSchema 
  7 | } from "@modelcontextprotocol/sdk/types.js";
  8 | import { ProtocolHandler } from "./ProtocolHandler.js";
  9 | import { ReadResourceRequest, CallToolRequest } from '@modelcontextprotocol/sdk/types.js';
 10 | 
 11 | export class MCPHandler {
 12 |   private server: Server;
 13 |   private protocolHandler: ProtocolHandler;
 14 | 
 15 |   constructor(protocolHandler: ProtocolHandler) {
 16 |     this.protocolHandler = protocolHandler;
 17 |     this.server = new Server({
 18 |       name: "minecraft-mcp-server",
 19 |       version: "1.0.0"
 20 |     }, {
 21 |       capabilities: {
 22 |         resources: {},
 23 |         tools: {}
 24 |       }
 25 |     });
 26 | 
 27 |     this.setupHandlers();
 28 |   }
 29 | 
 30 |   private setupHandlers(): void {
 31 |     this.setupResourceHandlers();
 32 |     this.setupToolHandlers();
 33 |   }
 34 | 
 35 |   private setupResourceHandlers(): void {
 36 |     // List available resources
 37 |     this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
 38 |       try {
 39 |         return {
 40 |           resources: [
 41 |             {
 42 |               uri: "minecraft://bot/location",
 43 |               name: "Bot Location",
 44 |               mimeType: "application/json",
 45 |               description: "Current bot location in the Minecraft world"
 46 |             },
 47 |             {
 48 |               uri: "minecraft://bot/status",
 49 |               name: "Bot Status",
 50 |               mimeType: "application/json",
 51 |               description: "Current status of the bot"
 52 |             }
 53 |           ]
 54 |         };
 55 |       } catch (error) {
 56 |         throw error;
 57 |       }
 58 |     });
 59 | 
 60 |     // Handle resource reading
 61 |     this.server.setRequestHandler(ReadResourceRequestSchema, async (request: ReadResourceRequest) => {
 62 |       try {
 63 |         switch (request.params.uri) {
 64 |           case "minecraft://bot/location": {
 65 |             const pos = this.protocolHandler.getPosition();
 66 |             if (!pos) throw new Error("Position not available");
 67 |             
 68 |             return {
 69 |               contents: [{
 70 |                 uri: request.params.uri,
 71 |                 mimeType: "application/json",
 72 |                 text: JSON.stringify({
 73 |                   x: Math.round(pos.x * 100) / 100,
 74 |                   y: Math.round(pos.y * 100) / 100,
 75 |                   z: Math.round(pos.z * 100) / 100
 76 |                 })
 77 |               }]
 78 |             };
 79 |           }
 80 | 
 81 |           case "minecraft://bot/status": {
 82 |             return {
 83 |               contents: [{
 84 |                 uri: request.params.uri,
 85 |                 mimeType: "application/json",
 86 |                 text: JSON.stringify({
 87 |                   connected: this.protocolHandler.isConnected()
 88 |                 })
 89 |               }]
 90 |             };
 91 |           }
 92 | 
 93 |           default:
 94 |             throw new Error(`Unknown resource: ${request.params.uri}`);
 95 |         }
 96 |       } catch (error) {
 97 |         throw error;
 98 |       }
 99 |     });
100 |   }
101 | 
102 |   private setupToolHandlers(): void {
103 |     // List available tools
104 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => {
105 |       try {
106 |         return {
107 |           tools: [
108 |             {
109 |               name: "chat",
110 |               description: "Send a chat message",
111 |               inputSchema: {
112 |                 type: "object",
113 |                 properties: {
114 |                   message: { type: "string" }
115 |                 },
116 |                 required: ["message"]
117 |               }
118 |             },
119 |             {
120 |               name: "jump",
121 |               description: "Make the bot jump",
122 |               inputSchema: {
123 |                 type: "object",
124 |                 properties: {}
125 |               }
126 |             },
127 |             {
128 |               name: "moveForward",
129 |               description: "Make the bot move forward",
130 |               inputSchema: {
131 |                 type: "object",
132 |                 properties: {}
133 |               }
134 |             },
135 |             {
136 |               name: "moveBack",
137 |               description: "Make the bot move backward",
138 |               inputSchema: {
139 |                 type: "object",
140 |                 properties: {}
141 |               }
142 |             },
143 |             {
144 |               name: "turnLeft",
145 |               description: "Make the bot turn left",
146 |               inputSchema: {
147 |                 type: "object",
148 |                 properties: {}
149 |               }
150 |             },
151 |             {
152 |               name: "turnRight",
153 |               description: "Make the bot turn right",
154 |               inputSchema: {
155 |                 type: "object",
156 |                 properties: {}
157 |               }
158 |             },
159 |             {
160 |               name: "placeBlock",
161 |               description: "Place a block at specified coordinates",
162 |               inputSchema: {
163 |                 type: "object",
164 |                 properties: {
165 |                   x: { type: "number" },
166 |                   y: { type: "number" },
167 |                   z: { type: "number" }
168 |                 },
169 |                 required: ["x", "y", "z"]
170 |               }
171 |             },
172 |             {
173 |               name: "digBlock",
174 |               description: "Break a block at specified coordinates",
175 |               inputSchema: {
176 |                 type: "object",
177 |                 properties: {
178 |                   x: { type: "number" },
179 |                   y: { type: "number" },
180 |                   z: { type: "number" }
181 |                 },
182 |                 required: ["x", "y", "z"]
183 |               }
184 |             },
185 |             {
186 |               name: "getBlockInfo",
187 |               description: "Get information about a block at specified coordinates",
188 |               inputSchema: {
189 |                 type: "object",
190 |                 properties: {
191 |                   x: { type: "number" },
192 |                   y: { type: "number" },
193 |                   z: { type: "number" }
194 |                 },
195 |                 required: ["x", "y", "z"]
196 |               }
197 |             },
198 |             {
199 |               name: "selectSlot",
200 |               description: "Select a hotbar slot (0-8)",
201 |               inputSchema: {
202 |                 type: "object",
203 |                 properties: {
204 |                   slot: { 
205 |                     type: "number",
206 |                     minimum: 0,
207 |                     maximum: 8
208 |                   }
209 |                 },
210 |                 required: ["slot"]
211 |               }
212 |             },
213 |             {
214 |               name: "getInventory",
215 |               description: "Get contents of bot's inventory",
216 |               inputSchema: {
217 |                 type: "object",
218 |                 properties: {}
219 |               }
220 |             },
221 |             {
222 |               name: "equipItem",
223 |               description: "Equip an item by name",
224 |               inputSchema: {
225 |                 type: "object",
226 |                 properties: {
227 |                   itemName: { type: "string" },
228 |                   destination: { 
229 |                     type: "string",
230 |                     enum: ["hand", "head", "torso", "legs", "feet"]
231 |                   }
232 |                 },
233 |                 required: ["itemName"]
234 |               }
235 |             },
236 |             {
237 |               name: "getStatus",
238 |               description: "Get bot's current status including health, food, position, etc.",
239 |               inputSchema: {
240 |                 type: "object",
241 |                 properties: {}
242 |               }
243 |             },
244 |             {
245 |               name: "getNearbyEntities",
246 |               description: "Get list of nearby entities within specified range",
247 |               inputSchema: {
248 |                 type: "object",
249 |                 properties: {
250 |                   range: {
251 |                     type: "number",
252 |                     minimum: 1,
253 |                     maximum: 100,
254 |                     default: 10
255 |                   }
256 |                 }
257 |               }
258 |             },
259 |             {
260 |               name: "attack",
261 |               description: "Attack a nearby entity by name",
262 |               inputSchema: {
263 |                 type: "object",
264 |                 properties: {
265 |                   entityName: { type: "string" }
266 |                 },
267 |                 required: ["entityName"]
268 |               }
269 |             },
270 |             {
271 |               name: "useItem",
272 |               description: "Use/activate the currently held item",
273 |               inputSchema: {
274 |                 type: "object",
275 |                 properties: {
276 |                   hand: { 
277 |                     type: "string",
278 |                     enum: ["right", "left"],
279 |                     default: "right"
280 |                   }
281 |                 }
282 |               }
283 |             },
284 |             {
285 |               name: "stopUsingItem",
286 |               description: "Stop using/deactivate the current item",
287 |               inputSchema: {
288 |                 type: "object",
289 |                 properties: {}
290 |               }
291 |             },
292 |             {
293 |               name: "lookAt",
294 |               description: "Make the bot look at specific coordinates",
295 |               inputSchema: {
296 |                 type: "object",
297 |                 properties: {
298 |                   x: { type: "number" },
299 |                   y: { type: "number" },
300 |                   z: { type: "number" }
301 |                 },
302 |                 required: ["x", "y", "z"]
303 |               }
304 |             },
305 |             {
306 |               name: "followPlayer",
307 |               description: "Follow a specific player",
308 |               inputSchema: {
309 |                 type: "object",
310 |                 properties: {
311 |                   playerName: { type: "string" }
312 |                 },
313 |                 required: ["playerName"]
314 |               }
315 |             },
316 |             {
317 |               name: "stopFollowing",
318 |               description: "Stop following current target",
319 |               inputSchema: {
320 |                 type: "object",
321 |                 properties: {}
322 |               }
323 |             },
324 |             {
325 |               name: "goToPosition",
326 |               description: "Navigate to specific coordinates",
327 |               inputSchema: {
328 |                 type: "object",
329 |                 properties: {
330 |                   x: { type: "number" },
331 |                   y: { type: "number" },
332 |                   z: { type: "number" }
333 |                 },
334 |                 required: ["x", "y", "z"]
335 |               }
336 |             }
337 |           ]
338 |         };
339 |       } catch (error) {
340 |         throw error;
341 |       }
342 |     });
343 | 
344 |     // Handle tool calls
345 |     this.server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
346 |       try {
347 |         switch (request.params.name) {
348 |           case "chat":
349 |             if (request.params.arguments && request.params.arguments.message) {
350 |               await this.protocolHandler.sendChat(request.params.arguments.message as string);
351 |               return {
352 |                 content: [{
353 |                   type: "text",
354 |                   text: "Message sent"
355 |                 }]
356 |               };
357 |             } else {
358 |               throw new Error("Invalid arguments for 'chat' tool");
359 |             }
360 | 
361 |           case "jump":
362 |             await this.protocolHandler.jump();
363 |             return {
364 |               content: [{
365 |                 type: "text",
366 |                 text: "Jumped!"
367 |               }]
368 |             };
369 | 
370 |           case "moveForward":
371 |             await this.protocolHandler.moveForward();
372 |             return {
373 |               content: [{ type: "text", text: "Moved forward" }]
374 |             };
375 | 
376 |           case "moveBack":
377 |             await this.protocolHandler.moveBack();
378 |             return {
379 |               content: [{ type: "text", text: "Moved backward" }]
380 |             };
381 | 
382 |           case "turnLeft":
383 |             await this.protocolHandler.turnLeft();
384 |             return {
385 |               content: [{ type: "text", text: "Turned left" }]
386 |             };
387 | 
388 |           case "turnRight":
389 |             await this.protocolHandler.turnRight();
390 |             return {
391 |               content: [{ type: "text", text: "Turned right" }]
392 |             };
393 | 
394 |           case "placeBlock": {
395 |             const { x, y, z } = request.params.arguments as { x: number, y: number, z: number };
396 |             await this.protocolHandler.placeBlock(x, y, z);
397 |             return {
398 |               content: [{ type: "text", text: `Placed block at (${x}, ${y}, ${z})` }]
399 |             };
400 |           }
401 | 
402 |           case "digBlock": {
403 |             const { x, y, z } = request.params.arguments as { x: number, y: number, z: number };
404 |             await this.protocolHandler.digBlock(x, y, z);
405 |             return {
406 |               content: [{ type: "text", text: `Broke block at (${x}, ${y}, ${z})` }]
407 |             };
408 |           }
409 | 
410 |           case "getBlockInfo": {
411 |             const { x, y, z } = request.params.arguments as { x: number, y: number, z: number };
412 |             const blockInfo = await this.protocolHandler.getBlockInfo(x, y, z);
413 |             return {
414 |               content: [{ type: "text", text: JSON.stringify(blockInfo, null, 2) }]
415 |             };
416 |           }
417 | 
418 |           case "selectSlot": {
419 |             const { slot } = request.params.arguments as { slot: number };
420 |             await this.protocolHandler.selectSlot(slot);
421 |             return {
422 |               content: [{ type: "text", text: `Selected slot ${slot}` }]
423 |             };
424 |           }
425 | 
426 |           case "getInventory": {
427 |             const inventory = await this.protocolHandler.getInventory();
428 |             return {
429 |               content: [{ type: "text", text: JSON.stringify(inventory, null, 2) }]
430 |             };
431 |           }
432 | 
433 |           case "equipItem": {
434 |             const { itemName, destination } = request.params.arguments as { 
435 |               itemName: string, 
436 |               destination?: string 
437 |             };
438 |             await this.protocolHandler.equipItem(itemName, destination);
439 |             return {
440 |               content: [{ 
441 |                 type: "text", 
442 |                 text: `Equipped ${itemName}${destination ? ` to ${destination}` : ''}`
443 |               }]
444 |             };
445 |           }
446 | 
447 |           case "getStatus": {
448 |             const status = await this.protocolHandler.getStatus();
449 |             return {
450 |               content: [{ type: "text", text: JSON.stringify(status, null, 2) }]
451 |             };
452 |           }
453 | 
454 |           case "getNearbyEntities": {
455 |             const { range } = request.params.arguments as { range?: number };
456 |             const entities = await this.protocolHandler.getNearbyEntities(range);
457 |             return {
458 |               content: [{ type: "text", text: JSON.stringify(entities, null, 2) }]
459 |             };
460 |           }
461 | 
462 |           case "attack": {
463 |             const { entityName } = request.params.arguments as { entityName: string };
464 |             await this.protocolHandler.attack(entityName);
465 |             return {
466 |               content: [{ type: "text", text: `Attacked entity: ${entityName}` }]
467 |             };
468 |           }
469 | 
470 |           case "useItem": {
471 |             const { hand = 'right' } = request.params.arguments as { hand?: 'right' | 'left' };
472 |             await this.protocolHandler.useItem(hand);
473 |             return {
474 |               content: [{ type: "text", text: `Used item in ${hand} hand` }]
475 |             };
476 |           }
477 | 
478 |           case "stopUsingItem": {
479 |             await this.protocolHandler.stopUsingItem();
480 |             return {
481 |               content: [{ type: "text", text: "Stopped using item" }]
482 |             };
483 |           }
484 | 
485 |           case "lookAt": {
486 |             const { x, y, z } = request.params.arguments as { x: number, y: number, z: number };
487 |             await this.protocolHandler.lookAt(x, y, z);
488 |             return {
489 |               content: [{ type: "text", text: `Looking at position (${x}, ${y}, ${z})` }]
490 |             };
491 |           }
492 | 
493 |           case "followPlayer": {
494 |             const { playerName } = request.params.arguments as { playerName: string };
495 |             await this.protocolHandler.followPlayer(playerName);
496 |             return {
497 |               content: [{ type: "text", text: `Following player: ${playerName}` }]
498 |             };
499 |           }
500 | 
501 |           case "stopFollowing": {
502 |             await this.protocolHandler.stopFollowing();
503 |             return {
504 |               content: [{ type: "text", text: "Stopped following" }]
505 |             };
506 |           }
507 | 
508 |           case "goToPosition": {
509 |             const { x, y, z } = request.params.arguments as { x: number, y: number, z: number };
510 |             await this.protocolHandler.goToPosition(x, y, z);
511 |             return {
512 |               content: [{ type: "text", text: `Moving to position (${x}, ${y}, ${z})` }]
513 |             };
514 |           }
515 | 
516 |           default:
517 |             throw new Error(`Unknown tool: ${request.params.name}`);
518 |         }
519 |       } catch (error) {
520 |         throw error;
521 |       }
522 |     });
523 |   }
524 | 
525 |   public getServer(): Server {
526 |     return this.server;
527 |   }
528 | } 
```