rubiks-cube-mcp-server
Version:
MCP server for Rubik's Cube solving with real-time 3D visualization and MCP UI integration
217 lines (216 loc) • 8.67 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
const zod_1 = require("zod");
const cubeLogic_js_1 = require("./cubeLogic.js");
const visualizationServer_js_1 = require("./visualizationServer.js");
class RubiksCubeMCPServer {
mcpServer;
visualizationServer;
games;
constructor() {
this.mcpServer = new mcp_js_1.McpServer({
name: "rubiks-cube-mcp-server",
version: "1.0.0"
});
this.visualizationServer = new visualizationServer_js_1.VisualizationServer();
this.games = new Map();
this.setupTools();
}
setupTools() {
// 큐브 게임 시작
this.mcpServer.tool("startCube", "Initialize a new Rubik's Cube game session", {
scramble: zod_1.z.boolean().optional().describe("Whether to scramble the cube initially"),
difficulty: zod_1.z.number().min(1).max(100).optional().describe("Number of scramble moves (1-100)")
}, async ({ scramble = true, difficulty = 20 }) => {
const gameId = `cube_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const cube = new cubeLogic_js_1.RubiksCube();
if (scramble) {
cube.scramble(difficulty);
}
const session = {
id: gameId,
cubeState: cube.getState(),
createdAt: Date.now(),
lastActivity: Date.now(),
status: 'active',
scrambleMoves: difficulty
};
this.games.set(gameId, { cube, session });
this.visualizationServer.registerSession(session);
const currentState = cube.getState();
const response = {
gameId,
cube: currentState,
scrambleMoves: difficulty,
nextAction: currentState.solved ? "finish" : "manipulateCube"
};
// MCP UI 리소스 생성 (예: 게임 링크)
const gameUrl = `http://localhost:3000/game/${gameId}`;
const uiResponse = {
type: "resource",
resource: {
uri: `ui://game-link/${gameId}`,
mimeType: "text/html",
text: `<a href="${gameUrl}" target="_blank">Click to Play!</a>`
}
};
return {
content: [
uiResponse,
{ type: "text", text: JSON.stringify(response, null, 2) }
]
};
});
// 게임 참여
this.mcpServer.tool("joinGame", "Join an existing Rubik's Cube game session", {
gameId: zod_1.z.string().describe("The game session ID to join"),
}, async ({ gameId }) => {
const game = this.games.get(gameId);
if (!game) {
throw new Error(`Game session ${gameId} not found`);
}
const { cube, session } = game;
const currentState = cube.getState();
const response = {
gameId,
cube: currentState,
scrambleMoves: session.scrambleMoves,
nextAction: currentState.solved ? "finish" : "manipulateCube",
};
return {
content: [
{ type: "text", text: "Joined game successfully." },
{ type: "text", text: JSON.stringify(response, null, 2) },
],
};
});
// 큐브 조작
this.mcpServer.tool("manipulateCube", "Execute a move on the Rubik's Cube", {
gameId: zod_1.z.string().describe("The game session ID"),
move: zod_1.z.enum(['U', 'D', 'L', 'R', 'F', 'B', 'U\'', 'D\'', 'L\'', 'R\'', 'F\'', 'B\'', 'U2', 'D2', 'L2', 'R2', 'F2', 'B2']).describe("The cube move to execute")
}, async ({ gameId, move }) => {
const game = this.games.get(gameId);
if (!game) {
throw new Error(`Game session ${gameId} not found`);
}
const { cube, session } = game;
// 이미 해결된 큐브인지 확인
if (session.status === 'completed') {
const response = {
gameId,
cube: cube.getState(),
nextAction: "finish"
};
return {
content: [
{
type: "text",
text: JSON.stringify(response, null, 2)
}
]
};
}
// 움직임 실행
cube.executeMove(move);
const newState = cube.getState();
// 세션 업데이트
session.cubeState = newState;
session.lastActivity = Date.now();
if (newState.solved) {
session.status = 'completed';
}
// 시각화 서버 업데이트
this.visualizationServer.updateSession(gameId, newState);
const response = {
gameId,
cube: newState,
nextAction: newState.solved ? "finish" : "manipulateCube"
};
return {
content: [
{
type: "text",
text: JSON.stringify(response, null, 2)
}
]
};
});
// 게임 완료
this.mcpServer.tool("finish", "Complete the Rubik's Cube game session", {
gameId: zod_1.z.string().describe("The game session ID")
}, async ({ gameId }) => {
const game = this.games.get(gameId);
if (!game) {
throw new Error(`Game session ${gameId} not found`);
}
const { cube, session } = game;
const finalState = cube.getState();
session.status = 'completed';
session.lastActivity = Date.now();
const response = {
gameId,
cube: finalState,
nextAction: null,
};
const message = finalState.solved
? `🎉 Congratulations! You solved the cube for game ${gameId}.`
: `Game ${gameId} finished. The cube was not solved.`;
return {
content: [
{ type: "text", text: message },
{ type: "text", text: JSON.stringify(response, null, 2) },
],
};
});
}
async start() {
// 시각화 서버 시작 - 환경변수 PORT 또는 기본값 3000 사용
const port = parseInt(process.env.PORT || '3000');
this.visualizationServer.start(port);
// MCP 서버 시작
const transport = new stdio_js_1.StdioServerTransport();
// Process exit handlers - parent process가 죽으면 함께 종료
process.on('SIGINT', () => {
console.error("🛑 SIGINT received, shutting down...");
this.shutdown();
});
process.on('SIGTERM', () => {
console.error("🛑 SIGTERM received, shutting down...");
this.shutdown();
});
// Stdio disconnect handler - MCP client 연결이 끊어지면 종료
process.stdin.on('end', () => {
console.error("🛑 Stdin disconnected, shutting down...");
this.shutdown();
});
process.stdin.on('close', () => {
console.error("🛑 Stdin closed, shutting down...");
this.shutdown();
});
await this.mcpServer.connect(transport);
console.error("🎲 Rubik's Cube MCP Server started!");
console.error("🌐 Visualization available at: http://localhost:3000");
}
shutdown() {
console.error("🔄 Shutting down servers...");
try {
// Visualization server 종료
this.visualizationServer.stop();
console.error("✅ Visualization server stopped");
}
catch (error) {
console.error("❌ Error stopping visualization server:", error);
}
// Process 종료
process.exit(0);
}
}
// 서버 시작
const server = new RubiksCubeMCPServer();
server.start().catch((error) => {
console.error("Failed to start server:", error);
process.exit(1);
});