Skip to main content
Glama
fritzprix
by fritzprix
refactoring.md6.94 kB
# Refactoring Plan ## 목표 - 게임 시작 시 scramble 동작에 대해 난이도(섞기 횟수)를 명확히 지정할 수 있도록 리팩터링한다. - MCP UI를 활용해 게임의 URL을 사용자가 클릭할 수 있는 링크 형태로 전달하는 기능을 추가한다. - 게임의 초기값(큐브 상태 등)도 함께 반환하여 클라이언트가 바로 렌더링할 수 있도록 한다. ## 문제점 - 현재 MCP 도구 `startCube`에서 scramble 옵션을 켜면 `RubiksCube.scramble()` 메서드가 항상 20번의 무작위 move로만 섞임. - `startCube` 도구에서 난이도(scramble move 횟수) 값을 전달할 수 있는 파라미터가 없음. - AI 에이전트가 다양한 난이도의 큐브 퍼즐을 요청할 수 없음 (항상 20-move scramble로 고정). - 웹 인터페이스나 API를 통해 사용자가 난이도를 조절할 수 없음. - 게임 시작 후, 사용자가 직접 클릭해서 게임을 시작할 수 있는 UI가 없음. - 게임의 초기값(큐브 상태 등)이 별도로 반환되지 않아 클라이언트가 상태를 관리하기 어렵다. ## 개선 방향 1. 함수 서명 변경 - MCP 도구 `startCube`에 `difficulty`(number) 파라미터를 추가하고 기본값을 20으로 설정. - Zod 스키마 예시: `difficulty: z.number().min(1).max(100).optional().describe("Number of scramble moves (1-100)")` 2. scramble 호출에 난이도 전달 - 기존: `if (scramble) { cube.scramble(); }` - 변경: `if (scramble) { cube.scramble(difficulty || 20); }` 3. API/핸들러 연동 - 클라이언트가 보낸 요청에서 `difficulty` 값을 읽어 MCP 서버에 전달하도록 수정. 4. 입력 검증 및 기본값 - `difficulty`는 양의 정수로 제한(예: 최소 1, 최대 100 또는 프로젝트 규칙에 맞게 설정). - 잘못된 값일 경우 기본값(20)으로 대체. 5. 게임의 초기값 반환 - 큐브의 상태, 난이도 등 게임의 초기값을 response 객체로 반환. 6. **게임 URL을 클릭할 수 있는 링크로 전달 (MCP UI 활용)** - 게임이 시작되면, 게임의 URL을 포함한 UI 리소스를 생성하여 MCP UI를 통해 클라이언트에 전달. - 사용자는 MCP UI 클라이언트에서 해당 링크를 클릭하여 게임을 시작할 수 있음. 7. **MCP UI 리소스와 게임 초기값을 동시에 반환** - `content: [uiResponse, response]` 형태로 MCP UI 리소스와 게임 초기값을 함께 반환. ## MCP UI 기본 개념 및 활용 예시 ### MCP UI란? - MCP UI는 서버에서 동적으로 생성한 UI 리소스를 클라이언트에 전달하고, 클라이언트가 이를 안전하게 렌더링할 수 있도록 하는 표준 프로토콜이다. - 대표적으로 `@mcp-ui/server`에서 `createUIResource`를 사용해 UI 리소스를 만들고, 클라이언트에서는 `@mcp-ui/client`의 `UIResourceRenderer`로 렌더링한다. ### 게임 URL을 링크로 전달하는 MCP UI 예시 #### 서버 코드 예시 (TypeScript) ```typescript this.mcpServer.tool( "startCube", "Initialize a new Rubik's Cube game session", { scramble: z.boolean().optional().describe("Whether to scramble the cube initially"), difficulty: z.number().min(1).max(100).optional().describe("Number of scramble moves (1-100)") }, async ({ scramble = true, difficulty = 20 }: { scramble?: boolean; difficulty?: number }) => { const gameId = `cube_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const cube = new RubiksCube(); if (scramble) { cube.scramble(difficulty); } const session: GameSession = { id: gameId, cubeState: cube.getState(), createdAt: Date.now(), lastActivity: Date.now(), status: 'active' }; this.games.set(gameId, { cube, session }); this.visualizationServer.registerSession(session); const currentState = cube.getState(); const response: CubeResponse = { 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">게임 시작하기</a>` } }; return { content: [ uiResponse, { type: "text", text: JSON.stringify(response, null, 2) } ] }; } ); ``` #### 클라이언트 코드 예시 (React) ```tsx import React from 'react'; import { UIResourceRenderer } from '@mcp-ui/client'; function App({ mcpResource }) { if ( mcpResource.type === 'resource' && mcpResource.resource.uri?.startsWith('ui://') ) { return ( <UIResourceRenderer resource={mcpResource.resource} onUIAction={(result) => { console.log('Action:', result); }} /> ); } return <p>Unsupported resource</p>; } ``` #### Web Component 예시 ```html <ui-resource-renderer resource='{ "mimeType": "text/html", "text": "<a href=\"https://game.example.com\" target=\"_blank\">게임 시작하기</a>" }' ></ui-resource-renderer> ``` ### MCP UI를 활용한 작업 흐름 1. 서버에서 게임 URL을 포함한 UI 리소스를 생성 (`createUIResource`) 2. 클라이언트에서 해당 리소스를 받아 `UIResourceRenderer`로 렌더링 3. 사용자는 UI에서 링크를 클릭하여 게임을 시작 4. 동시에 게임의 초기값(response)도 받아서 상태 관리 및 렌더링에 활용 ## 호출 예시 ```typescript // AI 에이전트 호출 예시 await mcpClient.call("startCube", { scramble: true, difficulty: 5 }); // 쉬운 난이도: 5번 섞기 await mcpClient.call("startCube", { scramble: true, difficulty: 30 }); // 어려운 난이도: 30번 섞기 await mcpClient.call("startCube", { scramble: false }); // 섞지 않고 시작 ``` ## 검증(권장) - TypeScript 컴파일 확인: `npm run build` 또는 `npm run dev` - 수동 테스트: MCP 도구 호출하여 난이도별 섞기 동작 및 UI 링크 반환 확인 - 웹 인터페이스 테스트: `http://localhost:3000/game/{gameId}`에서 3D 큐브 상태 확인 - MCP UI 클라이언트에서 링크 클릭 동작 및 초기값 활용 확인 ## 비고 - `scramble` 구현이 랜덤 move를 연속 호출하는 방식이라면, `difficulty=1`은 정확히 1번의 move가 적용된 상태를 의미함. - 클라이언트가 난이도를 사람이 읽기 쉬운 라벨(예: easy/medium/hard)으로 보낼 경우 서버에서 라벨을 숫자 move로 매핑하는 유틸을 추가하는 것을 권장. - MCP UI를 활용하면 다양한 형태의 UI(링크, 버튼, iframe 등)를 안전하게 전달할 수 있으므로, 게임 외에도 여러 인터랙티브 기능을 쉽게

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/fritzprix/rubiks-cube-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server