mcp2mqtt
- src
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;
}
}