UNPKG

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
#!/usr/bin/env node "use strict"; 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); });