#
tokens: 9940/50000 10/10 files
lines: off (toggle) GitHub
raw markdown copy
# 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:
--------------------------------------------------------------------------------

```
# Build output
/dist

# Dependencies
/node_modules
/libraries

# Server files
/versions
/world
/logs
/minecraft-server

# Configuration files
banned-ips.json
banned-players.json
ops.json
package-lock.json
server.properties
usercache.json
whitelist.json
eula.txt

# IDE and system files
.DS_Store
.env
*.log


```

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

```markdown
# Minecraft MCP Integration

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.

![Screenshot](/public/screenshot.png?quality=medium)

## Prerequisites

1. Minecraft Launcher
2. Node.js 18 or higher
3. Claude Desktop App
4. Java 21.0.5 (recommended)

> ⚠️ Note: Currently only tested on macOS/Linux. Windows compatibility is not guaranteed.

## Important Note

1. **Use the F3+P Shortcut**:
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.

![Focus Settings](/public/focus.png)

2. **Connection Issues on Claude Restart**:
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.

## Installation Steps

1. **Download and Setup Minecraft Server**
   - Download Minecraft server v1.21 from [mcversions.net/1.21](https://mcversions.net/download/1.21)
   - Install Java 21.0.5 if not already installed (other versions are untested)
   - Create a dedicated directory (e.g., `~/minecraft-server/`)
   - Place the downloaded `server.jar` file in this directory
   - Note down the absolute path to your `server.jar` file

2. **Install and Configure MCP Integration**
   
   Quick Install (Recommended):
   ```bash
   npx -y @smithery/cli install mcp-minecraft --client claude
   ```
   Follow the CLI prompts to complete the setup.

   Or Manual Setup:
   - Navigate to `~/Library/Application Support/Claude/claude_desktop_config.json`
   - Add the MCP server configuration:   
   ```json
   {
     "mcpServers": {
       "mcp-minecraft": {
         "command": "npx",
         "args": [
           "-y",
           "mcp-minecraft@latest",
           "--server-jar",
           "/absolute/path/to/minecraft-server/server.jar"
         ]
       }
     }
   }   
   ```
   > ⚠️ Replace `/absolute/path/to/minecraft-server/server.jar` with your actual server.jar path

4. **Launch Claude Desktop**
   - Start Claude Desktop after completing the configuration

5. **Connect to Server**
   - Open Minecraft Launcher
   - Install and launch Minecraft Java Edition **v1.21**
   - Click "Play" and Select "Multiplayer"
   - Click "Add Server"
   - Enter server details:
     - Server Name: `Minecraft Server`
     - Server Address: `localhost:25565`
   - Click "Done"

## Features

### Resources
The integration exposes these MCP resources:

- `minecraft://bot/location` - Current bot position in the world
- `minecraft://bot/status` - Bot connection status

### Tools
Available MCP tools:

- `chat` - Send chat messages to the server
- `jump` - Make the bot jump
- `moveForward` - Make the bot move forward
- `moveBack` - Make the bot move backward
- `turnLeft` - Make the bot turn left
- `turnRight` - Make the bot turn right
- `placeBlock` - Place a block at specified coordinates
- `digBlock` - Break a block at specified coordinates
- `getBlockInfo` - Get information about a block at specified coordinates
- `selectSlot` - Select a hotbar slot (0-8)
- `getInventory` - Get contents of bot's inventory
- `equipItem` - Equip an item by name to specified destination
- `getStatus` - Get bot's current status (health, food, position, etc.)
- `getNearbyEntities` - Get list of nearby entities within range
- `attack` - Attack a nearby entity by name
- `useItem` - Use/activate the currently held item
- `stopUsingItem` - Stop using/deactivate the current item
- `lookAt` - Make the bot look at specific coordinates
- `followPlayer` - Follow a specific player
- `stopFollowing` - Stop following current target
- `goToPosition` - Navigate to specific coordinates

## Technical Details

- Server runs in offline mode for local development
- Default memory allocation: 2GB
- Default port: 25565
- Bot username: MCPBot

## Troubleshooting

### Common Issues

1. **MCP Connection Failed**
   - Look for lingering Java processes
   - Terminate them manually:
      - Windows: Use Task Manager (untested)
      - Mac/Linux: 
         - Go to 'Activity Monitor' and 'Force Quit' java
   - Restart computer if process termination fails
   - Note: Latest version should auto-resolve these issues

2. **Server Won't Start**
   - Verify Java is installed
   - Check server.jar path is correct
   - Ensure port 25565 is available

3. **Can't Connect to Server**
   - Verify server is running (check logs)
   - Confirm you're using "localhost" as server address
   - Check firewall settings

### Logs Location
- Minecraft Server logs: Check the minecraft-server directory
- Claude Desktop logs: `~/Library/Logs/Claude/mcp*.log`

## Contributing

Contributions, big or small, are welcome!

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

```

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

```typescript
export interface MinecraftServerConfig {
  maxPlayers: number;
  port: number;
  serverJarPath: string;
  memoryAllocation: string;
  username: string;
  version: string;
} 
```

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "ts-node": {
    "esm": true,
    "experimentalSpecifierResolution": "node"
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}

```

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

```json
{
  "name": "mcp-minecraft",
  "version": "1.0.34",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node --loader ts-node/esm src/index.ts",
    "dev": "ts-node-dev --respawn --transpile-only --esm src/index.ts",
    "build": "tsc"
  },
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "@modelcontextprotocol/sdk": "latest",
    "@types/node": "^22.10.2",
    "minecraft-protocol": "^1.51.0",
    "mineflayer": "^4.23.0",
    "mineflayer-pathfinder": "^2.4.5",
    "ts-node": "^10.9.2",
    "typescript": "^5.7.2",
    "yargs": "^17.7.2"
  },
  "devDependencies": {
    "@types/yargs": "^17.0.33",
    "ts-node-dev": "^2.0.0"
  },
  "bin": {
    "minecraft-mcp": "./dist/index.js"
  }
}

```

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

```typescript
#!/usr/bin/env node

import { Application } from './Application.js';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import * as fs from 'fs';

const argv = yargs(hideBin(process.argv))
  .option('server-jar', {
    alias: 'j',
    type: 'string',
    description: 'Absolute path to the Minecraft server JAR file',
    demandOption: true
  })
  .scriptName('minecraft-mcp')
  .help()
  .parseSync();

if (!fs.existsSync(argv.serverJar)) {
  process.exit(1);
}

const app = new Application({
  serverJarPath: argv.serverJar
});

const cleanup = async () => {
  try {
    await app.stop();
    process.exit(0);
  } catch (error) {
    process.exit(1);
  }
};

process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);

process.on('uncaughtException', async (error) => {
  await cleanup();
});

process.on('unhandledRejection', async (error) => {
  await cleanup();
});

app.start().catch(() => {
  process.exit(1);
});

```

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

```typescript
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { ServerManager } from "./ServerManager.js";
import { ProtocolHandler } from "./ProtocolHandler.js";
import { MCPHandler } from "./MCPHandler.js";
import path from 'path';

interface ApplicationConfig {
  serverJarPath: string;
}

export class Application {
  private serverManager: ServerManager;
  private protocolHandler: ProtocolHandler;
  private mcpHandler: MCPHandler;
  private transport: StdioServerTransport | null = null;

  constructor(config: ApplicationConfig) {
    const serverPath = path.resolve(config.serverJarPath);
    
    // Initialize with config from CLI
    this.serverManager = new ServerManager({
      maxPlayers: 10,
      port: 25565,
      serverJarPath: serverPath,
      memoryAllocation: '2G',
      username: 'MCPBot',
      version: '1.21'
    });

    this.protocolHandler = new ProtocolHandler({
      host: 'localhost',
      port: 25565,
      username: 'MCPBot',
      version: '1.21'
    });

    this.mcpHandler = new MCPHandler(this.protocolHandler);

    this.setupEventHandlers();
  }

  private setupEventHandlers(): void {
    this.serverManager.on('log', (message) => {
    });

    this.serverManager.on('error', (error) => {
    });

    this.protocolHandler.on('chat', ({ username, message }) => {
    });

    this.protocolHandler.on('error', (error) => {
    });

    process.on('SIGINT', async () => {
      await this.shutdown();
      process.exit(0);
    });
  }

  public async start(): Promise<void> {
    try {
      // Start MCP server first - use only stdout for MCP communication
      this.transport = new StdioServerTransport();
      await this.mcpHandler.getServer().connect(this.transport);

      // Start Minecraft server
      await this.serverManager.start();

      // Wait a bit for the server to initialize
      await new Promise(resolve => setTimeout(resolve, 5000));

      // Connect bot
      await this.protocolHandler.connect();

    } catch (error) {
      await this.shutdown();
      process.exit(1);
    }
  }

  public async shutdown(): Promise<void> {
    if (this.mcpHandler && this.transport) {
      this.transport = null;
    }
    
    await this.protocolHandler.disconnect();
    await this.serverManager.stop();
  }

  async stop(): Promise<void> {
    try {
      // Disconnect MCP server
      if (this.mcpHandler && this.transport) {
        await this.mcpHandler.getServer().close();
        this.transport = null;
      }

      // Disconnect bot
      await this.protocolHandler.disconnect();

      // Stop Minecraft server
      await this.serverManager.stop();

    } catch (error) {
      throw error;
    }
  }
} 
```

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

```typescript
import { spawn, ChildProcess } from 'child_process';
import { EventEmitter } from 'events';
import { MinecraftServerConfig } from './types/config.js';
import * as fs from 'fs';
import path from 'path';
import * as os from 'os';

export class ServerManager extends EventEmitter {
  private process: ChildProcess | null = null;
  private config: MinecraftServerConfig;
  private isRunning: boolean = false;

  constructor(config: MinecraftServerConfig) {
    super();
    this.config = this.validateConfig(config);
    
    process.on('exit', () => {
      this.killProcess();
    });

    process.on('SIGTERM', () => {
      this.killProcess();
    });

    process.on('SIGINT', () => {
      this.killProcess();
    });
  }

  private killProcess(): void {
    if (this.process) {
      try {
        process.kill(-this.process.pid!, 'SIGKILL');
      } catch (error) {
        // Ignore errors during force kill
      }
      this.process = null;
      this.isRunning = false;
    }
  }

  private validateConfig(config: MinecraftServerConfig): MinecraftServerConfig {
    if (!config.serverJarPath) {
      throw new Error('Server JAR path is required');
    }
    if (!config.port || config.port < 1 || config.port > 65535) {
      throw new Error('Invalid port number');
    }
    return {
      maxPlayers: config.maxPlayers || 20,
      port: config.port,
      serverJarPath: config.serverJarPath,
      memoryAllocation: config.memoryAllocation || '2G',
      username: config.username || 'MCPBot',
      version: config.version || '1.21'
    };
  }

  private ensureEulaAccepted(): void {
    // Get the directory containing the server JAR
    const serverDir = path.dirname(this.config.serverJarPath);
    const eulaPath = path.join(serverDir, 'eula.txt');

    // Create or update eula.txt
    fs.writeFileSync(eulaPath, 'eula=true', 'utf8');
  }

  private ensureServerProperties(): void {
    const serverDir = path.dirname(this.config.serverJarPath);
    const propsPath = path.join(serverDir, 'server.properties');

    let properties = '';
    if (fs.existsSync(propsPath)) {
      properties = fs.readFileSync(propsPath, 'utf8');
    }

    // Define our server properties for a simple plains world
    const serverProperties = {
      'online-mode': 'false',
      'level-type': 'flat',
      'spawn-protection': '0',
      'difficulty': 'peaceful',          // No hostile mobs
      'spawn-monsters': 'false',         // Disable monster spawning
      'spawn-animals': 'true',           // Enable animal spawning
      'spawn-npcs': 'false',             // Disable villagers
      'generate-structures': 'false',     // Disable structures (villages, temples, etc)
      'allow-nether': 'false',           // Disable nether
      'gamemode': 'creative',            // Set creative mode for easier building
      'do-daylight-cycle': 'false',
      'max-players': this.config.maxPlayers.toString(),
      'server-port': this.config.port.toString(),
      'motd': 'Peaceful Plains Server'
    };

    // Update or create each property
    for (const [key, value] of Object.entries(serverProperties)) {
      const regex = new RegExp(`^${key}=.*$`, 'm');
      if (properties.match(regex)) {
        properties = properties.replace(regex, `${key}=${value}`);
      } else {
        properties += `\n${key}=${value}`;
      }
    }

    fs.writeFileSync(propsPath, properties.trim(), 'utf8');
  }

  private normalizePath(p: string): string {
    return path.normalize(p).toLowerCase();
  }

  private expandHome(filepath: string): string {
    if (filepath.startsWith("~/") || filepath === "~") {
      return path.join(os.homedir(), filepath.slice(1));
    }
    return filepath;
  }

  private validateServerPath(): string {
    const expandedPath = this.expandHome(this.config.serverJarPath);
    const absolutePath = path.isAbsolute(expandedPath) 
      ? path.resolve(expandedPath)
      : path.resolve(process.cwd(), expandedPath);
    
    if (!fs.existsSync(absolutePath)) {
      throw new Error(`Server JAR not found at path: ${absolutePath}`);
    }
    
    return absolutePath;
  }

  public async start(): Promise<void> {
    if (this.isRunning) {
      throw new Error('Server is already running');
    }

    return new Promise((resolve, reject) => {
      try {
        const serverJarPath = this.validateServerPath();
        const serverDir = path.dirname(serverJarPath);
        
        this.ensureEulaAccepted();
        this.ensureServerProperties();

        this.process = spawn('java', [
          `-Xmx${this.config.memoryAllocation}`,
          `-Xms${this.config.memoryAllocation}`,
          '-jar',
          serverJarPath,
          'nogui'
        ], {
          cwd: serverDir,
          stdio: ['pipe', 'pipe', 'pipe'],
          detached: true,
          ...(process.platform !== 'win32' && { pid: true })
        });

        const timeout = setTimeout(() => {
          reject(new Error('Server startup timed out'));
          this.stop();
        }, 60000);

        this.process.stdout?.on('data', (data: Buffer) => {
          const message = data.toString();
          
          if (message.includes('Done')) {
            clearTimeout(timeout);
            this.isRunning = true;
            resolve();
          }
        });

        this.process.stderr?.on('data', (data: Buffer) => {
          const error = data.toString();
          if (error.includes('Error')) {
            reject(new Error(error));
          }
        });

        this.process.on('close', (code) => {
          this.isRunning = false;
        });

        this.process.on('error', (err) => {
          this.isRunning = false;
          reject(err);
        });

      } catch (error) {
        reject(error);
      }
    });
  }

  public async stop(): Promise<void> {
    if (!this.isRunning || !this.process) {
      return;
    }

    return new Promise((resolve) => {
      const forceKillTimeout = setTimeout(() => {
        this.killProcess();
        resolve();
      }, 10000);

      this.process?.once('close', () => {
        clearTimeout(forceKillTimeout);
        this.isRunning = false;
        this.process = null;
        resolve();
      });
      
      if (this.process?.stdin) {
        this.process.stdin.write('stop\n');
      } else {
        this.killProcess();
        resolve();
      }
    });
  }

  public isServerRunning(): boolean {
    return this.isRunning;
  }
} 
```

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

```typescript
import * as mineflayer from 'mineflayer';
import { EventEmitter } from 'events';
import { Vec3 } from 'vec3';
import pathfinderPkg from 'mineflayer-pathfinder';
const { pathfinder, Movements, goals } = pathfinderPkg;

export interface BotConfig {
  host: string;
  port: number;
  username: string;
  version: string;
}

export class ProtocolHandler extends EventEmitter {
  private bot: mineflayer.Bot | null = null;
  private config: BotConfig;

  constructor(config: BotConfig) {
    super();
    this.config = config;
  }

  public async connect(): Promise<void> {
    if (this.bot) {
      throw new Error('Bot is already connected');
    }

    return new Promise((resolve, reject) => {
      try {
        this.bot = mineflayer.createBot({
          host: this.config.host,
          port: this.config.port,
          username: this.config.username,
          version: this.config.version
        });

        this.bot.once('spawn', () => {
          this.bot?.loadPlugin(pathfinder);
          
          if (this.bot?.pathfinder) {
            this.bot.pathfinder.setMovements(new Movements(this.bot));
          }

          this.setupEventHandlers();
          this.emit('connected');
          resolve();
        });

        this.bot.on('error', (error) => {
          this.emit('error', error);
          reject(error);
        });

      } catch (error) {
        reject(error);
      }
    });
  }

  private setupEventHandlers(): void {
    if (!this.bot) return;

    this.bot.on('chat', (username, message) => {
      this.emit('chat', { username, message });
    });

    this.bot.on('kicked', (reason) => {
      this.emit('kicked', reason);
    });

    this.bot.on('error', (error) => {
      this.emit('error', error);
    });
  }

  public async sendChat(message: string): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    await this.bot.chat(message);
  }

  public async jump(): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    this.bot.setControlState('jump', true);
    setTimeout(() => {
      if (this.bot) this.bot.setControlState('jump', false);
    }, 500);
  }

  public getPosition(): Vec3 | null {
    if (!this.bot || !this.bot.entity) return null;
    return this.bot.entity.position;
  }

  public async disconnect(): Promise<void> {
    if (!this.bot) return;
    
    return new Promise((resolve) => {
      const bot = this.bot;
      if (!bot) {
        resolve();
        return;
      }
      
      bot.removeAllListeners();
      
      bot.once('end', () => {
        this.bot = null;
        
        setTimeout(() => {
          process.exit(0);
        }, 1000);
        
        resolve();
      });
      
      bot.end();
    });
  }

  public isConnected(): boolean {
    return this.bot !== null;
  }

  public async moveForward(): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    this.bot.setControlState('forward', true);
    await new Promise(resolve => setTimeout(resolve, 1000));
    this.bot.setControlState('forward', false);
  }

  public async moveBack(): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    this.bot.setControlState('back', true);
    await new Promise(resolve => setTimeout(resolve, 1000));
    this.bot.setControlState('back', false);
  }

  public async turnLeft(): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    this.bot.setControlState('left', true);
    await new Promise(resolve => setTimeout(resolve, 500));
    this.bot.setControlState('left', false);
  }

  public async turnRight(): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    this.bot.setControlState('right', true);
    await new Promise(resolve => setTimeout(resolve, 500));
    this.bot.setControlState('right', false);
  }

  public async placeBlock(x: number, y: number, z: number): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    
    try {
      const targetPos = new Vec3(x, y, z);
      const faceVector = new Vec3(0, 1, 0);
      
      const referenceBlock = await this.bot.blockAt(targetPos);
      if (!referenceBlock) throw new Error('No reference block found');
      
      await this.bot.placeBlock(referenceBlock, faceVector);
    } catch (error) {
      throw new Error(`Failed to place block: ${error}`);
    }
  }

  public async digBlock(x: number, y: number, z: number): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    
    try {
      const targetPos = new Vec3(x, y, z);
      const block = await this.bot.blockAt(targetPos);
      
      if (!block) throw new Error('No block at target position');
      if (block.name === 'air') throw new Error('Cannot dig air');
      
      await this.bot.dig(block);
    } catch (error) {
      throw new Error(`Failed to dig block: ${error}`);
    }
  }

  public async getBlockInfo(x: number, y: number, z: number): Promise<any> {
    if (!this.bot) throw new Error('Bot not connected');
    
    try {
      const targetPos = new Vec3(x, y, z);
      const block = await this.bot.blockAt(targetPos);
      
      if (!block) throw new Error('No block at target position');
      
      return {
        name: block.name,
        type: block.type,
        position: {
          x: block.position.x,
          y: block.position.y,
          z: block.position.z
        },
        hardness: block.hardness
      };
    } catch (error) {
      throw new Error(`Failed to get block info: ${error}`);
    }
  }

  public async selectSlot(slot: number): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    if (slot < 0 || slot > 8) throw new Error('Slot must be between 0 and 8');
    
    try {
      await this.bot.setQuickBarSlot(slot);
    } catch (error) {
      throw new Error(`Failed to select slot: ${error}`);
    }
  }

  public async getInventory(): Promise<any> {
    if (!this.bot) throw new Error('Bot not connected');
    
    const items = this.bot.inventory.items();
    return items.map(item => ({
      name: item.name,
      count: item.count,
      slot: item.slot,
      displayName: item.displayName
    }));
  }

  public async equipItem(itemName: string, destination?: string): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    
    try {
        const item = this.bot.inventory.items().find(item => item.name.includes(itemName));
        if (!item) throw new Error(`Item ${itemName} not found in inventory`);
        
        const equipDestination: mineflayer.EquipmentDestination | null = destination as mineflayer.EquipmentDestination || null;
        await this.bot.equip(item, equipDestination);
    } catch (error) {
        throw new Error(`Failed to equip item: ${error}`);
    }
  }

  public async getStatus(): Promise<any> {
    if (!this.bot) throw new Error('Bot not connected');
    
    return {
        health: this.bot.health,
        food: this.bot.food,
        gameMode: this.bot.game?.gameMode ?? 'unknown',
        position: this.getPosition(),
        isRaining: this.bot.isRaining,
        time: {
            timeOfDay: this.bot.time?.timeOfDay ?? 0,
            day: this.bot.time?.day ?? 0
        }
    };
  }

  public async getNearbyEntities(range: number = 10): Promise<any[]> {
    if (!this.bot) throw new Error('Bot not connected');
    
    return Object.values(this.bot.entities)
        .filter(entity => {
            if (!entity.position || !this.bot?.entity?.position) return false;
            return entity.position.distanceTo(this.bot.entity.position) <= range;
        })
        .map(entity => ({
            name: entity.name,
            type: entity.type,
            position: entity.position,
            distance: entity.position && this.bot?.entity?.position 
                ? entity.position.distanceTo(this.bot.entity.position) 
                : null // Handle the case where position might be null
        }));
  }

  public async attack(entityName: string): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    
    try {
      const entity = Object.values(this.bot.entities)
        .find(e => e.name === entityName && 
          e.position.distanceTo(this.bot!.entity.position) <= 4);
      
      if (!entity) throw new Error('Entity not found or too far');
      await this.bot.attack(entity);
    } catch (error) {
      throw new Error(`Failed to attack: ${error}`);
    }
  }

  public async useItem(hand: 'right' | 'left' = 'right'): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    
    try {
      await this.bot.activateItem(hand === 'right');
    } catch (error) {
      throw new Error(`Failed to use item: ${error}`);
    }
  }

  public async stopUsingItem(): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    
    try {
      await this.bot.deactivateItem();
    } catch (error) {
      throw new Error(`Failed to stop using item: ${error}`);
    }
  }

  public async lookAt(x: number, y: number, z: number): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    
    try {
      await this.bot.lookAt(new Vec3(x, y, z));
    } catch (error) {
      throw new Error(`Failed to look at position: ${error}`);
    }
  }

  public async followPlayer(playerName: string): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    
    try {
        const player = this.bot.players[playerName]?.entity;
        if (!player) throw new Error('Player not found');

        // Follow at 2 blocks distance
        await this.bot.pathfinder.goto(
            new goals.GoalFollow(player, 2)
        );
    } catch (error) {
        throw new Error(`Failed to follow player: ${error}`);
    }
  }

  public async stopFollowing(): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    this.bot.pathfinder.stop();
  }

  public async goToPosition(x: number, y: number, z: number): Promise<void> {
    if (!this.bot) throw new Error('Bot not connected');
    
    try {
        await this.bot.pathfinder.goto(
            new goals.GoalBlock(x, y, z)
        );
    } catch (error) {
        throw new Error(`Failed to go to position: ${error}`);
    }
  }
} 
```

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

```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { 
  ListResourcesRequestSchema, 
  ReadResourceRequestSchema,
  ListToolsRequestSchema,
  CallToolRequestSchema 
} from "@modelcontextprotocol/sdk/types.js";
import { ProtocolHandler } from "./ProtocolHandler.js";
import { ReadResourceRequest, CallToolRequest } from '@modelcontextprotocol/sdk/types.js';

export class MCPHandler {
  private server: Server;
  private protocolHandler: ProtocolHandler;

  constructor(protocolHandler: ProtocolHandler) {
    this.protocolHandler = protocolHandler;
    this.server = new Server({
      name: "minecraft-mcp-server",
      version: "1.0.0"
    }, {
      capabilities: {
        resources: {},
        tools: {}
      }
    });

    this.setupHandlers();
  }

  private setupHandlers(): void {
    this.setupResourceHandlers();
    this.setupToolHandlers();
  }

  private setupResourceHandlers(): void {
    // List available resources
    this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
      try {
        return {
          resources: [
            {
              uri: "minecraft://bot/location",
              name: "Bot Location",
              mimeType: "application/json",
              description: "Current bot location in the Minecraft world"
            },
            {
              uri: "minecraft://bot/status",
              name: "Bot Status",
              mimeType: "application/json",
              description: "Current status of the bot"
            }
          ]
        };
      } catch (error) {
        throw error;
      }
    });

    // Handle resource reading
    this.server.setRequestHandler(ReadResourceRequestSchema, async (request: ReadResourceRequest) => {
      try {
        switch (request.params.uri) {
          case "minecraft://bot/location": {
            const pos = this.protocolHandler.getPosition();
            if (!pos) throw new Error("Position not available");
            
            return {
              contents: [{
                uri: request.params.uri,
                mimeType: "application/json",
                text: JSON.stringify({
                  x: Math.round(pos.x * 100) / 100,
                  y: Math.round(pos.y * 100) / 100,
                  z: Math.round(pos.z * 100) / 100
                })
              }]
            };
          }

          case "minecraft://bot/status": {
            return {
              contents: [{
                uri: request.params.uri,
                mimeType: "application/json",
                text: JSON.stringify({
                  connected: this.protocolHandler.isConnected()
                })
              }]
            };
          }

          default:
            throw new Error(`Unknown resource: ${request.params.uri}`);
        }
      } catch (error) {
        throw error;
      }
    });
  }

  private setupToolHandlers(): void {
    // List available tools
    this.server.setRequestHandler(ListToolsRequestSchema, async () => {
      try {
        return {
          tools: [
            {
              name: "chat",
              description: "Send a chat message",
              inputSchema: {
                type: "object",
                properties: {
                  message: { type: "string" }
                },
                required: ["message"]
              }
            },
            {
              name: "jump",
              description: "Make the bot jump",
              inputSchema: {
                type: "object",
                properties: {}
              }
            },
            {
              name: "moveForward",
              description: "Make the bot move forward",
              inputSchema: {
                type: "object",
                properties: {}
              }
            },
            {
              name: "moveBack",
              description: "Make the bot move backward",
              inputSchema: {
                type: "object",
                properties: {}
              }
            },
            {
              name: "turnLeft",
              description: "Make the bot turn left",
              inputSchema: {
                type: "object",
                properties: {}
              }
            },
            {
              name: "turnRight",
              description: "Make the bot turn right",
              inputSchema: {
                type: "object",
                properties: {}
              }
            },
            {
              name: "placeBlock",
              description: "Place a block at specified coordinates",
              inputSchema: {
                type: "object",
                properties: {
                  x: { type: "number" },
                  y: { type: "number" },
                  z: { type: "number" }
                },
                required: ["x", "y", "z"]
              }
            },
            {
              name: "digBlock",
              description: "Break a block at specified coordinates",
              inputSchema: {
                type: "object",
                properties: {
                  x: { type: "number" },
                  y: { type: "number" },
                  z: { type: "number" }
                },
                required: ["x", "y", "z"]
              }
            },
            {
              name: "getBlockInfo",
              description: "Get information about a block at specified coordinates",
              inputSchema: {
                type: "object",
                properties: {
                  x: { type: "number" },
                  y: { type: "number" },
                  z: { type: "number" }
                },
                required: ["x", "y", "z"]
              }
            },
            {
              name: "selectSlot",
              description: "Select a hotbar slot (0-8)",
              inputSchema: {
                type: "object",
                properties: {
                  slot: { 
                    type: "number",
                    minimum: 0,
                    maximum: 8
                  }
                },
                required: ["slot"]
              }
            },
            {
              name: "getInventory",
              description: "Get contents of bot's inventory",
              inputSchema: {
                type: "object",
                properties: {}
              }
            },
            {
              name: "equipItem",
              description: "Equip an item by name",
              inputSchema: {
                type: "object",
                properties: {
                  itemName: { type: "string" },
                  destination: { 
                    type: "string",
                    enum: ["hand", "head", "torso", "legs", "feet"]
                  }
                },
                required: ["itemName"]
              }
            },
            {
              name: "getStatus",
              description: "Get bot's current status including health, food, position, etc.",
              inputSchema: {
                type: "object",
                properties: {}
              }
            },
            {
              name: "getNearbyEntities",
              description: "Get list of nearby entities within specified range",
              inputSchema: {
                type: "object",
                properties: {
                  range: {
                    type: "number",
                    minimum: 1,
                    maximum: 100,
                    default: 10
                  }
                }
              }
            },
            {
              name: "attack",
              description: "Attack a nearby entity by name",
              inputSchema: {
                type: "object",
                properties: {
                  entityName: { type: "string" }
                },
                required: ["entityName"]
              }
            },
            {
              name: "useItem",
              description: "Use/activate the currently held item",
              inputSchema: {
                type: "object",
                properties: {
                  hand: { 
                    type: "string",
                    enum: ["right", "left"],
                    default: "right"
                  }
                }
              }
            },
            {
              name: "stopUsingItem",
              description: "Stop using/deactivate the current item",
              inputSchema: {
                type: "object",
                properties: {}
              }
            },
            {
              name: "lookAt",
              description: "Make the bot look at specific coordinates",
              inputSchema: {
                type: "object",
                properties: {
                  x: { type: "number" },
                  y: { type: "number" },
                  z: { type: "number" }
                },
                required: ["x", "y", "z"]
              }
            },
            {
              name: "followPlayer",
              description: "Follow a specific player",
              inputSchema: {
                type: "object",
                properties: {
                  playerName: { type: "string" }
                },
                required: ["playerName"]
              }
            },
            {
              name: "stopFollowing",
              description: "Stop following current target",
              inputSchema: {
                type: "object",
                properties: {}
              }
            },
            {
              name: "goToPosition",
              description: "Navigate to specific coordinates",
              inputSchema: {
                type: "object",
                properties: {
                  x: { type: "number" },
                  y: { type: "number" },
                  z: { type: "number" }
                },
                required: ["x", "y", "z"]
              }
            }
          ]
        };
      } catch (error) {
        throw error;
      }
    });

    // Handle tool calls
    this.server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
      try {
        switch (request.params.name) {
          case "chat":
            if (request.params.arguments && request.params.arguments.message) {
              await this.protocolHandler.sendChat(request.params.arguments.message as string);
              return {
                content: [{
                  type: "text",
                  text: "Message sent"
                }]
              };
            } else {
              throw new Error("Invalid arguments for 'chat' tool");
            }

          case "jump":
            await this.protocolHandler.jump();
            return {
              content: [{
                type: "text",
                text: "Jumped!"
              }]
            };

          case "moveForward":
            await this.protocolHandler.moveForward();
            return {
              content: [{ type: "text", text: "Moved forward" }]
            };

          case "moveBack":
            await this.protocolHandler.moveBack();
            return {
              content: [{ type: "text", text: "Moved backward" }]
            };

          case "turnLeft":
            await this.protocolHandler.turnLeft();
            return {
              content: [{ type: "text", text: "Turned left" }]
            };

          case "turnRight":
            await this.protocolHandler.turnRight();
            return {
              content: [{ type: "text", text: "Turned right" }]
            };

          case "placeBlock": {
            const { x, y, z } = request.params.arguments as { x: number, y: number, z: number };
            await this.protocolHandler.placeBlock(x, y, z);
            return {
              content: [{ type: "text", text: `Placed block at (${x}, ${y}, ${z})` }]
            };
          }

          case "digBlock": {
            const { x, y, z } = request.params.arguments as { x: number, y: number, z: number };
            await this.protocolHandler.digBlock(x, y, z);
            return {
              content: [{ type: "text", text: `Broke block at (${x}, ${y}, ${z})` }]
            };
          }

          case "getBlockInfo": {
            const { x, y, z } = request.params.arguments as { x: number, y: number, z: number };
            const blockInfo = await this.protocolHandler.getBlockInfo(x, y, z);
            return {
              content: [{ type: "text", text: JSON.stringify(blockInfo, null, 2) }]
            };
          }

          case "selectSlot": {
            const { slot } = request.params.arguments as { slot: number };
            await this.protocolHandler.selectSlot(slot);
            return {
              content: [{ type: "text", text: `Selected slot ${slot}` }]
            };
          }

          case "getInventory": {
            const inventory = await this.protocolHandler.getInventory();
            return {
              content: [{ type: "text", text: JSON.stringify(inventory, null, 2) }]
            };
          }

          case "equipItem": {
            const { itemName, destination } = request.params.arguments as { 
              itemName: string, 
              destination?: string 
            };
            await this.protocolHandler.equipItem(itemName, destination);
            return {
              content: [{ 
                type: "text", 
                text: `Equipped ${itemName}${destination ? ` to ${destination}` : ''}`
              }]
            };
          }

          case "getStatus": {
            const status = await this.protocolHandler.getStatus();
            return {
              content: [{ type: "text", text: JSON.stringify(status, null, 2) }]
            };
          }

          case "getNearbyEntities": {
            const { range } = request.params.arguments as { range?: number };
            const entities = await this.protocolHandler.getNearbyEntities(range);
            return {
              content: [{ type: "text", text: JSON.stringify(entities, null, 2) }]
            };
          }

          case "attack": {
            const { entityName } = request.params.arguments as { entityName: string };
            await this.protocolHandler.attack(entityName);
            return {
              content: [{ type: "text", text: `Attacked entity: ${entityName}` }]
            };
          }

          case "useItem": {
            const { hand = 'right' } = request.params.arguments as { hand?: 'right' | 'left' };
            await this.protocolHandler.useItem(hand);
            return {
              content: [{ type: "text", text: `Used item in ${hand} hand` }]
            };
          }

          case "stopUsingItem": {
            await this.protocolHandler.stopUsingItem();
            return {
              content: [{ type: "text", text: "Stopped using item" }]
            };
          }

          case "lookAt": {
            const { x, y, z } = request.params.arguments as { x: number, y: number, z: number };
            await this.protocolHandler.lookAt(x, y, z);
            return {
              content: [{ type: "text", text: `Looking at position (${x}, ${y}, ${z})` }]
            };
          }

          case "followPlayer": {
            const { playerName } = request.params.arguments as { playerName: string };
            await this.protocolHandler.followPlayer(playerName);
            return {
              content: [{ type: "text", text: `Following player: ${playerName}` }]
            };
          }

          case "stopFollowing": {
            await this.protocolHandler.stopFollowing();
            return {
              content: [{ type: "text", text: "Stopped following" }]
            };
          }

          case "goToPosition": {
            const { x, y, z } = request.params.arguments as { x: number, y: number, z: number };
            await this.protocolHandler.goToPosition(x, y, z);
            return {
              content: [{ type: "text", text: `Moving to position (${x}, ${y}, ${z})` }]
            };
          }

          default:
            throw new Error(`Unknown tool: ${request.params.name}`);
        }
      } catch (error) {
        throw error;
      }
    });
  }

  public getServer(): Server {
    return this.server;
  }
} 
```