Skip to main content
Glama
README.md
# Eden MCP

Eden Nintendo Switch 에뮬레이터를 로컬에서 자동화하기 위한 Model Context Protocol(MCP)
서버입니다. 게임 실행과 종료, 독립 세션, 키보드 및 Switch 버튼 입력, 터치, 네이티브
스크린샷, 로그 조회, base+update 실행을 하나의 MCP 인터페이스로 제공합니다.

- MCP 릴리즈: <https://github.com/Leuconoe/eden-mcp/releases/tag/v0.5.0>
- control-enabled Eden 릴리즈: <https://github.com/Leuconoe/eden/releases/tag/eden-mcp-v0.5.0>
- 대응 Eden 소스: <https://github.com/Leuconoe/eden/tree/eden-mcp-v0.5.0>
- 기준 Eden 커밋: `a43664c0fd9bb56d2bb4ef3b4f1943a19b767066`

## 반드시 커스텀 Eden 빌드를 사용해야 하는 이유

> [!IMPORTANT]
> Eden MCP의 전체 기능을 사용하려면 `eden-control` protocol 2가 포함된 커스텀 Eden
> 빌드가 필요합니다. 가장 간단한 방법은 올바른 `eden.exe`가 이미 포함된
> [Windows x64 포터블 번들](https://github.com/Leuconoe/eden-mcp/releases/tag/v0.5.0)을
> 사용하는 것입니다.

공식 또는 미패치 Eden은 Windows 데스크톱 자동화 폴백으로만 동작합니다. 이 폴백은 화면에
보이는 창에 키와 단일 포인터 입력을 보내는 방식이므로 전체 MCP 기능을 대체할 수 없습니다.
`eden-mcp.toml`의 `require_control=true`를 유지하면 잘못된 `eden.exe`로 교체됐을 때 조용히
기능이 축소되지 않고 명확한 오류가 발생합니다.

| 기능 | 공식/미패치 Eden 폴백 | 커스텀 `eden-control` 빌드 |
|---|---:|---:|
| Base 게임 실행 | 가능 | 가능 |
| 별도 update 설치 후 실행 | 불가 | 가능 |
| 호스트 키보드 입력 | 가능 | 가능 |
| Switch 버튼 직접 입력 | 불가 | 가능 |
| 단일 터치 | 가능 | 가능 |
| 멀티터치 | 불가 | 가능, finger ID 0-15 |
| 창이 가려진 상태의 네이티브 스크린샷 | 불가 | 가능 |
| 첫 프레임 준비 완료 확인 | 불가 | 가능 |
| 명시적 로그 flush | 불가 | 가능 |
| 인증된 정상 종료 | 불가 | 가능 |

커스텀 빌드만 별도로 필요한 경우
[Eden control build](https://github.com/Leuconoe/eden/releases/tag/eden-mcp-v0.5.0)를
받을 수 있습니다. 해당 빌드의 전체 업스트림 히스토리는 `master`에 보존되어 있고,
`eden-mcp-control` 브랜치는 기준 커밋보다 control endpoint 커밋 하나만 앞서 있습니다.

## 주요 기능

- 포터블 폴더를 이동해도 유지되는 상대 경로 기반 설정
- 기본 세션과 복제된 독립 프로필을 사용하는 병렬 관리 세션
- 프로세스별 랜덤 토큰으로 인증되는 `127.0.0.1` 전용 JSONL endpoint
- Eden 입력 서브시스템을 통한 키보드 및 Switch virtual gamepad 입력
- 16개 finger ID를 지원하는 터치, long press, swipe 및 down/move/up
- 렌더러가 직접 저장하는 PNG 스크린샷
- 별도 update NSP를 세션 NAND에 설치한 후 base 게임 실행
- 타임아웃 후 프로세스를 유지하고 다시 기다릴 수 있는 실행 상태 머신
- base/update title ID 정규화, sampled fingerprint 및 LayeredFS 배치 사전검증
- 정지된 관리 세션에만 허용되는 원자적 patch staging
- 시간차 프레임의 frozen/periodic loop 판정과 결정적 입력 스크립트
- 크기 제한, 줄 수 제한 및 문자열 필터를 지원하는 로그 조회
- 회전·truncate를 감지하는 불투명 증분 로그 cursor와 redacted evidence ZIP
- Eden/RyuBing 공통 capability contract, runtime 지표 및 호환성 실험 advisor
- 실행 파일, 프로필, 키, NAND, 허용 경로, 저장 공간 및 protocol을 검사하는 진단 도구
- 프로필 복사 전 저장 공간 검사와 실패한 세션 생성의 제한적 롤백
- MCP가 시작한 Eden만 종료하고 사용자가 별도로 실행한 Eden은 건드리지 않는 프로세스 소유권

## 요구 사항

- 포터블 Eden 실행 파일은 Windows x64용입니다.
- MCP 서버는 Python 3.11 이상과 [`uv`](https://docs.astral.sh/uv/)를 사용합니다.
- Vulkan/OpenGL 및 Eden의 일반 런타임 요구 사항이 필요합니다.
- 사용자는 자신이 합법적으로 확보한 키와 펌웨어를 Eden에 직접 설치해야 합니다.

릴리즈에는 console key, firmware, NAND 데이터, 게임, update, save, screenshot이 포함되지
않습니다. 이러한 파일은 저장소나 릴리즈 자산에 업로드하지 마십시오.

## 포터블 설치

1. [Eden MCP v0.5.0](https://github.com/Leuconoe/eden-mcp/releases/tag/v0.5.0)에서
   `eden-mcp-portable-win-x64-v0.5.0.zip`을 받습니다.
2. 쓰기 가능한 폴더에 압축을 풉니다.
3. 필요하면 `eden.exe`를 직접 한 번 실행해 `user/` 구조를 초기화합니다.
4. Eden의 일반 UI를 통해 사용자 소유 키와 펌웨어를 설치합니다.
5. `run-eden-mcp.cmd`를 MCP 클라이언트의 실행 명령으로 등록합니다.
6. 게임 실행 전 `eden_check_environment(probe_control=true)`를 호출합니다.

정상 포터블 레이아웃은 다음과 같습니다.

```text
eden-mcp-release-v0.5.0/
|-- eden-mcp.toml
|-- eden.exe
|-- eden_mcp-0.5.0-py3-none-any.whl
|-- run-eden-mcp.cmd
|-- README.txt
|-- LICENSE-MCP-MIT.txt
|-- LICENSE-Eden-GPL-3.0.txt
|-- user/
|   |-- config/qt-config.ini
|   |-- keys/                       # 사용자가 직접 준비
|   `-- nand/                       # 사용자가 직접 준비
`-- sessions/                       # 관리 세션 생성 시 자동 생성
```

`run-eden-mcp.cmd`는 번들 폴더를 현재 디렉터리로 사용하므로 정상 포터블 실행에는 환경변수가
필요하지 않습니다.

## MCP 클라이언트 설정

포터블 번들은 실행 스크립트 하나만 등록하면 됩니다. 다음 경로는 압축을 푼 실제 위치로
바꾸십시오.

```toml
[mcp_servers.eden]
command = "D:\\Apps\\eden-mcp-release-v0.5.0\\run-eden-mcp.cmd"
```

소스 체크아웃에서 실행하려면 다음과 같이 등록할 수 있습니다.

```toml
[mcp_servers.eden]
command = "uv"
args = ["--directory", "D:\\src\\eden-mcp", "run", "eden-mcp"]
```

기기별 경로나 정책을 MCP 저장소 밖에 두려면 외부 TOML 경로 하나만 주입하는 방식을
권장합니다.

```toml
[mcp_servers.eden.env]
EDEN_CONFIG_FILE = "D:\\private-config\\eden-mcp.toml"
```

환경변수는 서버 시작 시 읽는 외부 override일 뿐이며 MCP가 자신의 프로세스 환경을
수정하지 않습니다. Eden 자식 프로세스에는 MCP 설정 변수를 전달하지 않고, endpoint에 필요한
일회성 `EDEN_MCP_PORT`와 랜덤 `EDEN_MCP_TOKEN`만 추가합니다.

## 제공 도구

모든 일반 제어 도구는 선택적 `session_id`를 받습니다. 생략하거나 `default`를 전달하면 기본
세션을 사용하고, `eden_create_session`이 반환한 ID를 전달하면 해당 독립 세션을 사용합니다.

| 도구 | 설명 |
|---|---|
| `eden_start_game` | Base 게임을 실행하고 선택적으로 별도 update를 먼저 적용합니다. |
| `eden_wait_for_state` | 타임아웃된 기존 프로세스에서 control/game-ready 대기를 계속합니다. |
| `eden_validate_launch` | base/update/title ID/fingerprint/patch layout을 읽기 전용으로 검사합니다. |
| `eden_stop` | 이 MCP가 시작한 기본 Eden 프로세스를 종료합니다. |
| `eden_create_session` | 타이틀별 canonical 세션을 재사용하고, 없을 때만 기본 설정 프로필에서 복제합니다. |
| `eden_list_sessions` | 기본/관리 세션의 상태, PID 및 프로필 경로를 조회합니다. |
| `eden_check_environment` | 실행 파일, 프로필, 저장 공간 및 endpoint 호환성을 검사합니다. |
| `eden_stop_session` | 관리 세션을 종료하고 선택적으로 복제 프로필을 제거합니다. |
| `eden_get_session_storage` | 세션 폴더별 소유권 분류와 사용 용량을 삭제 없이 조회합니다. |
| `eden_cleanup_sessions` | 종료된 MCP 소유 세션에만 dry-run 우선 보존 정책을 적용합니다. |
| `eden_stage_patch` | 정지된 관리 세션의 격리된 `user/load`에 patch를 원자적으로 복사합니다. |
| `eden_inject_title_key` | 정지된 관리 세션의 `title.keys`에 rights ID/title key 매핑을 원자적으로 주입합니다. |
| `eden_status` | backend, 프로세스, 게임, 화면, readiness 및 capability를 반환합니다. |
| `eden_get_capabilities` | 공통 capability contract 1.0을 세션별로 반환합니다. |
| `eden_get_screen_size` | 터치와 스크린샷에 사용할 현재 좌표 공간을 반환합니다. |
| `eden_press_key` | 호스트 키보드의 down/up/tap 입력을 보냅니다. |
| `eden_press_button` | 플레이어 0-9에 Switch 버튼을 직접 보냅니다. |
| `eden_run_input_script` | chord, wait, touch, visual checkpoint를 검증 후 순서대로 실행합니다. |
| `eden_touch_screen` | tap, long press, swipe 또는 down/move/up 터치를 보냅니다. |
| `eden_take_screenshot` | 현재 렌더 화면을 PNG로 반환하고 선택적으로 파일에 저장합니다. |
| `eden_capture_sequence` | 2-12개 프레임을 비교해 frozen/periodic/moving을 판정합니다. |
| `eden_get_logs` | 제한된 로그와 구조화 entry 및 다음 호출용 opaque cursor를 반환합니다. |
| `eden_diagnose_game_issue` | 상태·상세 로그·반복 패턴을 묶어 플레이 실패 원인 후보를 보고합니다. |
| `eden_export_diagnostics` | 경로를 가린 report/log/선택적 screenshot evidence ZIP을 만듭니다. |
| `eden_get_runtime_metrics` | status와 로그에서 FPS/frame-time/shader/video 신호를 요약합니다. |
| `eden_advise_compatibility` | 설정을 바꾸지 않고 가역적인 호환성 실험을 제안합니다. |
| `eden_list_artifacts` | MCP가 생성한 진단 번들의 크기와 시각을 조회합니다. |
| `eden_cleanup_artifacts` | 기본 dry-run으로 진단 번들에만 보존 정책을 적용합니다. |

### 지원 입력

- 키보드: A-Z, 0-9, F1-F24, 방향키, Enter, Escape, Space, Tab, Backspace, Delete,
  Home, End, PageUp/PageDown, Shift, Ctrl, Alt, Meta
- Switch 버튼: A/B/X/Y, L/R/ZL/ZR, Plus/Minus, D-pad, LStick/RStick, SL/SR,
  Home, Capture
- 키와 버튼 action: `tap`, `down`, `up`
- 터치 action: `tap`, `long_press`, `swipe`, `down`, `move`, `up`

터치 좌표는 최신 스크린샷 또는 `eden_get_screen_size`가 반환한 픽셀 공간을 기준으로 합니다.
크기가 변경된 이미지를 기준으로 좌표를 재사용할 때는 `source_width`와 `source_height`를 함께
전달해야 합니다.

## 권장 사용 흐름

### 단일 게임

1. `eden_check_environment(probe_control=true)`
2. `eden_validate_launch(base_path=...)`
3. `eden_start_game(base_path=..., wait_until="game_ready")`
4. `launch.target_reached=true`를 확인합니다. 타임아웃이면 같은 `launch_id`로
   `eden_wait_for_state`를 호출합니다.
5. `eden_take_screenshot` 또는 `eden_capture_sequence`
6. 입력/터치 또는 `eden_run_input_script`
7. 문제가 있으면 `eden_export_diagnostics(focus="all")`
8. `eden_stop`

### 느린 시작과 재시도

`eden_start_game`의 기본 `wait_until`은 `game_ready`입니다. 반환되는 `launch`에는
`launch_id`, 현재 `phase`, 목표, 단계별 UTC 시각, `timed_out`, `last_error`,
`retryable`이 들어갑니다.

```text
eden_start_game(
  base_path="E:/NSW/_titles/_waitng/Example [0100000000000000].nsp",
  wait_until="game_ready",
  timeout_s=20,
  keep_running_on_timeout=true
)
```

20초 안에 shader compilation이 끝나지 않아도 프로세스는 종료되지 않습니다. 새 프로세스를
시작하지 말고 응답의 ID로 이어서 기다립니다.

```text
eden_wait_for_state(
  target="game_ready",
  timeout_s=120,
  launch_id="<start 응답의 launch_id>"
)
```

단계는 `process_started` → `control_ready` → update가 있으면 `load_requested` →
`game_ready` 순서입니다. update load 요청은 재시도 중 중복 전송되지 않습니다. 기존처럼
타임아웃 즉시 중단해야 하는 호출만 `keep_running_on_timeout=false`를 사용하십시오.

### 실행 전 content 및 patch 검사

`eden_validate_launch`는 파일 전체를 해시하지 않고 앞/뒤 각 64 KiB와 크기로 sampled SHA-256을
만듭니다. 파일명에서 16자리 title ID와 `[v숫자]`를 찾고, 일반적인 update ID
`base + 0x800`을 base application ID로 정규화합니다. 따라서 정상적인
`0100071022110000` base와 `0100071022110800` update는 같은 application으로 판정됩니다.

```text
eden_validate_launch(
  base_path="E:/Games/Game [0100071022110000][v0].nsp",
  update_path="E:/Games/Game [0100071022110800][v131072].nsp",
  application_id="0100071022110000",
  patch_path="E:/Patches/Game-Korean",
  expect_patch=true
)
```

지원하지 않는 확장자, 빈 파일, 같은 파일을 base/update로 중복 지정한 경우, 정규화된
application ID가 다른 경우는 `overall="error"`이며 실제 launch도 차단됩니다. 파일명에
metadata가 없는 경우와 흔한 `romfs`/`exefs`/`cheats` marker가 없는 경우는 warning입니다.
암호화 content 내부 metadata를 복호화하지 않으므로 최종 판정은 Eden loader 로그와 함께
확인해야 합니다.

결과에는 Eden이 실제로 사용할 것으로 추정한 `effective_profile_root`와
`effective_load_root`도 포함됩니다. 관리 세션은 세션 옆의 `user/`를 우선하고, 기본
비포터블 실행은 `EDEN_USER_DIR` 또는 Windows의 `%APPDATA%\eden` fallback을 사용합니다.
따라서 `expect_patch=true`가 실행 파일 옆의 존재하지 않는 `user/load`를 잘못 검사하는
문제를 피할 수 있습니다.

### Base + update

```text
eden_start_game(
  base_path="D:/games/title-base.nsp",
  update_path="D:/games/title-update.nsp"
)
```

별도 update는 커스텀 endpoint가 반드시 필요합니다. 현재 구현은 Eden의 기존 Install NSP
동작을 사용하므로 선택한 프로필 NAND에 update가 지속적으로 설치됩니다. 원본 base/update
파일은 수정하지 않습니다. 일회성 테스트에는 독립 관리 세션을 권장합니다.

외부 update의 session-local 사용은 관리 세션에서 실행하면 됩니다. 복제된 NAND에만
설치되며 `eden_stop_session(remove_profile=true)` 전까지 증거가 보존됩니다. DLC를 임의로
mount하는 control API는 아직 없으므로 capability contract에서 지원되는 것으로 광고하지
않습니다.

### 격리 patch staging

기본 프로필에는 patch를 자동 복사하지 않습니다. 먼저 관리 세션을 만들고 정지 상태에서
unpacked patch를 staging합니다.

```text
eden_stage_patch(
  session_id="qa-ko",
  application_id="0100071022110000",
  patch_path="E:/Patches/Game-Korean",
  label="korean-v1"
)
```

대상은 `sessions/qa-ko/user/load/0100071022110000/korean-v1`입니다. 임시 폴더에 복사를
끝낸 뒤 rename하며, 실패하면 그 시도가 만든 임시/대상만 롤백합니다. 같은 application ID와
동일한 파일 트리 SHA-256이 이미 staging되어 있으면 기존 경로를 반환하고 `reused=true`로
표시하므로 label만 달리해 같은 patch를 중복 복사하지 않습니다. default 세션, 실행 중인
세션, source와 session 경로가 겹치는 경우, link/junction 탈출, key/NAND/firmware/save형
루트는 거부됩니다. `patch_path`는 `romfs`, `exefs`, `cheats` 중 하나 이상을 직접 포함하는
상위 디렉터리여야 하며 `romfs` 자체를 넘기면 복사 전에 거부됩니다.

기본적으로 타이틀당 서로 다른 활성 patch도 하나만 허용합니다. 기존 patch와 나란히 비교할
특별한 사유가 있을 때만 `allow_multiple=true`와 비어 있지 않은 `reason`을 같이 전달합니다.
기존 patch를 덮어쓰거나 암묵적으로 폐기하지 않으며, 동일 내용은 이 예외 없이도 기존 staging을
재사용합니다.

패치 매니페스트는 디렉터리 publish 전에 원자적으로 기록됩니다. MCP가 중단되어도 다음
호출에서 존재하지 않는 대상은 재사용하지 않고, 대상 트리가 변조된 경우에도 저장된
SHA-256만 믿고 재사용하지 않습니다. Windows에서 목적지 경로가 너무 길면 복사 전에
짧은 session ID/label 또는 더 짧은 `session_root`를 사용하라는 오류를 반환하며, 일시적인
rename 공유 위반은 제한된 횟수로 재시도합니다.
응답에는 `active_patch_count`와 `restart_required`도 포함되어 static injection과 실제
런타임 적용을 구분할 수 있습니다. 관리 세션은 정지 상태에서만 stage되므로 반환된 patch는
다음 시작에 적용되며, 이미 실행 중인 타이틀에 hot-reload되었다고 간주하지 않습니다.

### 격리 title key 주입

자신이 합법적으로 확보한 ROM의 title key가 관리 세션에 없을 때, 128-bit rights ID와
128-bit title key를 각각 32자리 hexadecimal 문자열로 전달할 수 있습니다.

```text
eden_inject_title_key(
  session_id="qa-ko",
  rights_id="<32-hex-rights-id>",
  title_key="<32-hex-title-key>"
)
```

이 도구는 정지된 관리 세션의 `user/keys/title.keys`만 원자적으로 갱신합니다. ROM, profile
template, `default` 프로필, `prod.keys`는 수정하지 않습니다. 같은 매핑은 파일을 다시 쓰지
않으며, 같은 rights ID에 다른 키가 있으면 기본적으로 거부합니다. 기존 매핑을 의도적으로
교체할 때만 `overwrite_existing=true`를 사용하십시오.

응답에는 title key 원문이나 digest가 포함되지 않습니다. 다만 title key는 MCP 도구의 입력
인자이므로 호출 기록을 보존하는 클라이언트에서는 민감한 값으로 취급해야 합니다. 현재
`eden-control`에는 key reload endpoint가 없으므로 실행 중 세션은 거부되며, 주입한 매핑은
다음 `eden_start_game`에서 읽힙니다. 이 도구는 ROM에서 rights ID 또는 키를 추출하거나
다운로드하지 않습니다.

### 타이틀별 세션 재사용

일반적인 관리 세션은 application ID를 지정해 얻습니다.

```text
eden_create_session(application_id="0100071022110000")
```

첫 호출은 `title-0100071022110000` canonical 세션을 만들고, 이후 같은 base 또는 update
application ID 호출은 새 디렉터리를 만들지 않고 같은 `session_id`와 `reused=true`를
반환합니다. `eden_stop_session(remove_profile=false)`로 정지한 뒤나 MCP 서버가 재시작된
뒤에도 유효한 매니페스트와 프로필이 남아 있으면 `reuse_source="persisted"`로 다시 연결합니다.
관리 세션에서 다른 타이틀을 시작하려 하면 프로세스 실행 전에 거부됩니다.
idle 상태도 현재 MCP PID로 claim하므로 다른 MCP 서버가 같은 canonical 프로필을 동시에
복원하거나 정리하지 못합니다. 소유 MCP가 종료되면 다음 서버가 안전하게 claim합니다.

새 canonical 세션은 설정된 `profile_template` 전체를 복제합니다. template이 없으면 Eden이
실제로 사용하는 기본 프로필을 fallback으로 사용합니다. 복제에는 `config`, `keys`, `nand`
및 등록 firmware가 포함되므로 기존에 설정한 `prod.keys`와 firmware를 세션마다 다시
주입할 필요가 없습니다. 기본 프로필에 non-empty `keys/prod.keys` 또는
`nand/system/Contents/registered` firmware가 없으면 불완전한 세션을 만들지 않고 즉시
설정 오류를 반환합니다. 키 내용은 읽거나 응답하지 않습니다.

동일 타이틀에 정말 별도 프로필이 필요한 clean-room 비교나 동시 실행만 예외로 둡니다.

```text
eden_create_session(
  application_id="0100071022110000",
  force_new=true,
  reason="parallel clean-room comparison"
)
```

`force_new=true`는 비어 있지 않은 사유가 필수이며 매니페스트에 `session_role="exception"`과
함께 기록됩니다. application ID와 session ID를 모두 생략하는 무작위 세션 생성은 거부됩니다.
이전 클라이언트가 명시적 `session_id`만 주는 호환 경로는 유지되지만, 첫 managed launch에서
타이틀에 바인딩되며 이미 canonical 세션이 있으면 재사용 안내와 함께 거부됩니다.

관리 세션은 실행 파일을 hardlink하거나 복사하고, 기본 `user/` 프로필을 복제한 뒤 NAND,
SDMC, load, dump, TAS, screenshot 경로를 세션 내부로 다시 씁니다. save, update, screenshot,
log가 세션별로 분리됩니다. 종료 시 기본값 `remove_profile=false`를 사용해야 다음 호출에서
재사용됩니다. 즉시 폐기가 필요한 예외 세션만 `remove_profile=true`를 사용하십시오. 장기
보존할 실패 증거는 `protect_profile=true`로 종료합니다.

기본 보존 정책은 최신 종료 세션 3개를 남기고, 7일보다 오래됐거나 개수 제한을 넘은 종료
세션을 정리 후보로 계산합니다. 생성 전과 종료 후에는 이 후보의 dry-run 미리보기만 반환하며
어떤 프로필도 암묵적으로 삭제하지 않습니다. 기존 중복/legacy 폴더도 아래 storage dry-run으로
별도 확인해야 합니다.

### 세션 저장 공간과 중복 방지

`eden_get_session_storage`는 `session_root` 바로 아래 폴더만 읽어 `active`, `reclaimable`,
`protected`, `unconfirmed`, `legacy`로 분류하고 각 폴더의 byte 수를 반환합니다. 삭제 전에는
`eden_cleanup_sessions(dry_run=true)`로 정확한 후보를 확인할 수 있으며, 실제 적용은
`dry_run=false`일 때만 수행됩니다.

보존 후보 계산과 명시적 정리는 유효한 `.eden-mcp-session.json` 소유권 매니페스트가 있고 상태가
`stopped`이거나, 비정상 종료로 `running`으로 남았지만 PID가 더 이상 존재하지 않는
프로필만 삭제합니다. 실행 중인 세션, 보호 세션, 생성 도중 상태가 불명확한 세션, 매니페스트가
없는 이전 버전 폴더는 건드리지 않습니다. 따라서
기존 설치를 처음 업그레이드했을 때 오래된 폴더가 자동 삭제되지는 않으며, 필요하면 사용자가
내용을 확인한 후 별도로 정리해야 합니다.

`eden_list_sessions`는 현재 MCP 프로세스가 소유한 세션뿐 아니라 유효한 매니페스트가 남은
이전 프로세스의 세션도 `ownership="persisted_unowned"`로 읽기 전용 표시합니다. 다른 MCP
프로세스가 살아 있는 세션에는 attach/stop하지 않으며, 실제 정리는 먼저 storage dry-run으로
확인해야 합니다.

생성/종료 응답의 보존 후보 미리보기를 끄려면 `auto_prune_sessions=false`로 설정합니다.
이 설정이 true여도 실제 삭제는 발생하지 않으며, 실제 적용은 반드시
`eden_cleanup_sessions(dry_run=false)`로 명시해야 합니다. `session_retention_days=0`은 종료된 프로필을
나이 기준으로 즉시 후보화하므로, 실패 증거를 남겨야 하는 테스트에는
`protect_profile=true`를 사용하십시오.

## 플레이 실패 상세 진단

`eden_get_logs`는 사람이 빠르게 tail을 읽을 때 사용하고, 재현 실패를 MCP에 전달할 때는
`eden_diagnose_game_issue`를 사용합니다. 이 도구는 에뮬레이터를 중지하거나 설정을 변경하지
않으며 다음 자료를 한 JSON 응답으로 묶습니다.

- 현재 또는 마지막 실행의 PID, 종료 코드, backend, base/update 경로와 시작 오류
- 현재 `eden_log.txt` 또는 비어 있을 때의 회전 로그
- 원문 로그, 파싱된 severity/category/source/message, warning/error 주변 문맥
- severity 개수와 숫자·주소를 정규화한 반복 메시지
- 네 가지 주요 장애 유형의 confidence, 근거 및 다음 확인 작업
- 화면만으로 확인할 수 있는 사항과 로그 판정의 한계

일반적인 수집 호출:

```text
eden_diagnose_game_issue(
  focus="all",
  tail_lines=2000,
  context_lines=2,
  max_events=100,
  max_bytes=2000000,
  session_id="qa-01"
)
```

`session_id`는 문제를 재현한 세션과 같아야 합니다. 장애 직후, 다른 게임을 실행하거나 관리
프로필을 삭제하기 전에 호출하는 것이 좋습니다. 상태 조회나 control endpoint의 `flush_logs`가
실패해도 읽을 수 있는 로그와 실행 문맥은 그대로 반환하고, 실패한 부분만 `errors`에 기록합니다.

### 화면 loop 증거

로그 반복만으로 화면 반복을 확정하지 않습니다. `eden_capture_sequence(count=4,
interval_ms=1000)`는 각 PNG의 SHA-256과 64×64 grayscale perceptual hash, 이전 프레임 대비
정규화 차이를 반환합니다.

- `frozen`: 모든 인접 차이가 threshold 이하
- `periodic`: 2 이상 주기로 같은 프레임 패턴이 반복
- `moving`: 위 두 조건에 해당하지 않음

기본 threshold는 `0.01`이며 결과는 원인 판정이 아니라 시각 증거입니다. 이미지 합계가 24
MiB를 넘으면 metadata만 반환합니다. `include_images=false`로 처음부터 image block을 생략할
수 있습니다.

시퀀스 도중 control endpoint가 사라지면 이미 수집한 frame/hash와 부분 분석을 버리지 않고
`complete=false` 및 `terminal_failure`로 반환합니다. 단일 screenshot이나 runtime metrics도
원시 WinError 대신 PID, 종료 코드, 종료 귀속 상태, 재시도 권고를 구조화합니다. 종료 주체를
증명할 수 없으면 `shutdown_attribution="unresolved"`로 남기며 임의로 정상 종료로 간주하지
않습니다.

### 결정적 입력 스크립트

최대 100 step, 선언된 wait/hold/timeout 합계 5분까지 허용합니다. 모든 step을 먼저 검증한 후
실행하며, 실패하거나 기본 `release_at_end=true`이면 explicit `down` 입력을 역순으로
해제합니다.

```text
eden_run_input_script(steps=[
  {"op":"button_chord","buttons":["L","R"],"hold_ms":120},
  {"op":"button","button":"A","action":"tap","hold_ms":80},
  {"op":"wait_visual_change","timeout_ms":10000,"poll_ms":500,"threshold":0.01},
  {"op":"touch","x":640,"y":620,"action":"tap",
   "source_width":1280,"source_height":720}
])
```

지원 op는 `wait`, `button`, `button_chord`, `key`, `touch`,
`wait_visual_change`입니다. 각 step의 성공/실패와 소요 시간이 반환됩니다. analog는
`eden-control` protocol 2에 없어 사전검증 단계에서 명확히 거부합니다.

### 증분 로그와 evidence ZIP

`eden_get_logs` 응답의 `next_cursor`를 다음 호출의 `cursor`로 전달하면 새 로그만 받습니다.
cursor는 file identity와 byte offset을 감춘 문자열이며 log rotation 또는 truncate를 감지하면
`cursor_reset=true`와 이유를 반환합니다. `minimum_level`과 `contains`를 함께 사용할 수
있습니다.

`eden_export_diagnostics`는 `.eden-mcp/diagnostics` 아래에 directory와 ZIP을 만들며 다음
생성 파일만 허용합니다.

- 경로와 secret assignment를 가린 `report.json`
- report의 제한된 entry로 만든 `logs.ndjson`
- 명시적으로 `include_screenshot=true`일 때만 `screenshot.png`
- 크기, SHA-256, 제외 항목을 기록한 `manifest.json`

key, firmware, NAND, game/update/DLC, patch, save, config, 환경변수와 endpoint token은 탐색하거나
복사하지 않습니다. screenshot 자체에 민감한 화면이 있을 수 있으므로 기본값은 false입니다.
`eden_cleanup_artifacts`는 이 naming convention의 생성물만 대상으로 하며 기본값이
`dry_run=true`입니다.

### 공통 capability와 운영 관측

`eden_get_capabilities`와 `emulator://eden/capabilities` resource는
`emulator-mcp-capabilities` 1.0 계약을 반환합니다. 각 기능은 `available`, `conditional`,
`unavailable` 중 하나이며 같은 구조를 RyuBing MCP에서도 사용합니다. 클라이언트는 이 응답을
보고 analog, multi-touch, update 같은 backend 조건을 호출 전에 분기할 수 있습니다.

`eden_get_runtime_metrics`는 control status의 frame counter가 있을 때 관측 FPS를 계산하고,
최근 로그에서 FPS/frame-time sample, shader event와 video failure를 요약합니다. 없는 값은
추정하지 않고 null 또는 빈 sample로 남깁니다. `eden_advise_compatibility`는 이 증거와 진단
hint로 가역적인 비교 실험을 제안할 뿐 설정을 자동 변경하지 않습니다.

### 네 가지 focus

| `focus` | 주로 찾는 신호 | 해석 및 추가 확인 |
|---|---|---|
| `game_load_failure` | loader/boot/NCA/NSP/XCI/key/firmware 관련 warning·error, 비정상 종료, 시작 오류 | base/update title ID, keys와 firmware, 파일 무결성을 확인합니다. |
| `patch_loop` | patch/mod/RomFS/ExeFS/LayeredFS의 실패·retry·reload·loop가 3회 이상 반복 | 같은 화면이 반복되는지는 시간차 스크린샷으로 별도 확인합니다. |
| `video_loop` | video/movie/NVDEC/FFmpeg/codec/decoder/demux 오류 또는 반복 | decoder/backend 설정을 확인하고 여러 프레임이 실제로 같은지 비교합니다. |
| `patch_not_applied` | patch가 missing/skipped/disabled/invalid/failed이거나, 지정한 update의 성공 적용 기록이 없음 | mount 성공 후에도 한글 출력은 스크린샷, 언어 설정, font glyph와 patch 우선순위로 확인합니다. |
| `all` | 위 네 유형을 모두 평가 | 원인을 모를 때 사용하는 기본값입니다. |

`focus="patch_not_applied"`는 “패치는 있는데 한글이 나오지 않음”을 조사할 때도 사용합니다.
로그에 `PatchRomFS ... applied successfully`가 있으면 패치 적용 자체는 성공 근거로 남기되,
화면의 한국어 문자열과 글리프가 정상이라는 뜻으로 단정하지 않습니다. 이 경우 low-confidence
hint와 함께 최신 스크린샷/OCR, 게임 내 언어, 폰트 범위, 중복 mod 우선순위 확인을 권고합니다.

반복 메시지도 그 자체로 장애는 아닙니다. 예를 들어 여러 update에 대한 정상
`applied successfully` 기록은 `repeatedMessages`에는 남지만 `patch_loop`로 분류하지 않습니다.
실패·retry·invalid 같은 부정 신호가 함께 반복될 때만 loop 후보가 됩니다.

### 응답 읽기

주요 필드는 다음과 같습니다.

| 필드 | 의미 |
|---|---|
| `reportVersion`, `capturedAt`, `focus`, `sessionId` | 보고서 계약, 수집 시각과 대상 |
| `status` | 현재 및 마지막 launch/process 문맥 |
| `logs.available`, `selectedPath`, `usedRotated` | 실제 읽은 로그와 회전 로그 사용 여부 |
| `logs.text`, `logs.entries` | 제한된 원문과 구조화된 전체 line |
| `observations.logSummary` | severity 개수, truncation, 3회 이상 반복 메시지 |
| `observations.events` | warning/error/실패 신호 및 앞뒤 문맥 |
| `observations.hints` | 장애 분류, confidence, evidence, summary, recommendations |
| `errors` | 일부 수집 단계만 실패했을 때의 상세 오류 |
| `limitations` | 로그만으로 확정할 수 없는 화면 기반 증상 |

기본 제한은 tail 2,000줄/2MB이며 `tail_lines`는 1-10,000, `max_bytes`는
1,024-20,000,000, `context_lines`는 0-10, `max_events`는 1-500 범위입니다. 원문에는
로컬 경로, 하드웨어/드라이버 정보가 포함될 수 있습니다. MCP는 외부로 업로드하지 않습니다.
원본 JSON을 직접 공유할 때는 `logs.text`, `logs.entries`, 경로를 검토하고, 기본 경로
redaction과 allowlist ZIP이 필요한 경우 `eden_export_diagnostics`를 사용하십시오.

분류명과 상위 응답 구조는 [RyuBing MCP](https://github.com/Leuconoe/ryubing-mcp)의
`ryubing_get_diagnostics`와 맞췄습니다. 두 서버를 함께 쓰는 QA 도구는 같은 네 focus를 사용할 수
있고, Eden은 원문 주변 문맥과 종료 후 launch 정보를 추가로 제공합니다.

## 포터블 설정

기본 [`eden-mcp.toml`](eden-mcp.toml):

```toml
[portable]
root = "."
executable = "eden.exe"
profile_template = "user"
session_root = "sessions"
allowed_game_dirs = []

require_control = true
session_min_free_mb = 512
max_sessions = 4
session_retention_keep_latest = 3
session_retention_days = 7
auto_prune_sessions = true

startup_timeout = 20.0
control_timeout = 20.0
request_timeout = 30.0
update_timeout = 600.0
```

상대 경로는 `portable.root` 기준으로 해석됩니다. 설정 우선순위는 다음과 같습니다.

1. MCP 클라이언트가 외부에서 주입한 개별 환경변수
2. `eden-mcp.toml`의 `[portable]` 값
3. 내장 포터블 기본값

`allowed_game_dirs=[]`는 게임 경로 제한이 없다는 뜻입니다. 무인 자동화나 공유 시스템에서는
허용할 루트만 명시하는 것을 권장합니다.

```toml
allowed_game_dirs = ["D:/Games/Switch", "E:/TestTitles"]
```

허용 경로가 설정되면 canonical path가 해당 루트 밖에 있는 base/update 파일은 실행 전에
거부됩니다.

### 외부 override 환경변수

| 변수 | 의미 | 포터블 기본값 |
|---|---|---|
| `EDEN_CONFIG_FILE` | 명시적 TOML 경로 | `<portable-root>/eden-mcp.toml` |
| `EDEN_PORTABLE_ROOT` | TOML 탐색 및 내장 기본값의 루트 | frozen executable 폴더 또는 CWD |
| `EDEN_EXECUTABLE` | `eden.exe` 경로 | `eden.exe` |
| `EDEN_LOG_PATH` | 명시적 로그 경로 | 자동 검색 |
| `EDEN_USER_DIR` | 기본 Eden이 실제로 사용할 user/profile 경로 | `%APPDATA%\eden` fallback |
| `EDEN_PROFILE_TEMPLATE` | 세션별로 복제할 포터블 프로필 | `user` |
| `EDEN_SESSION_ROOT` | 관리 세션 상위 폴더 | `sessions` |
| `EDEN_ALLOWED_GAME_DIRS` | Windows에서 `;`로 구분한 허용 게임 루트 | 제한 없음 |
| `EDEN_MAX_SESSIONS` | 동시 관리 세션 제한, 최대 32 | `4` |
| `EDEN_SESSION_MIN_FREE_MB` | 프로필 복제 후 유지할 여유 공간 | `512` |
| `EDEN_SESSION_KEEP_LATEST` | 자동 정리에서 보존할 최신 종료 세션 수 | `3` |
| `EDEN_SESSION_RETENTION_DAYS` | 이 일수보다 오래된 종료 세션 정리 | `7` |
| `EDEN_AUTO_PRUNE_SESSIONS` | 생성 전·종료 후 안전한 자동 정리 실행 | `true` |
| `EDEN_REQUIRE_CONTROL` | 미패치/비호환 Eden 거부 여부 | `true` |
| `EDEN_STARTUP_TIMEOUT` | 창 및 game-ready 대기 시간(초) | `20` |
| `EDEN_CONTROL_TIMEOUT` | endpoint 탐지 시간(초) | `20` |
| `EDEN_REQUEST_TIMEOUT` | 일반 endpoint 요청 시간(초) | `30` |
| `EDEN_UPDATE_TIMEOUT` | update 설치/실행 시간(초) | `600` |

TOML에서 알 수 없는 설정명이나 잘못된 타입을 사용하면 오타를 무시하지 않고 시작 오류를
반환합니다.

## 환경 진단

`eden_check_environment(probe_control=false)`는 다음 정적 항목을 검사합니다.

- TOML 및 포터블 루트
- `eden.exe` 존재와 접근 가능 여부
- profile template과 `qt-config.ini`
- key 파일 및 NAND 폴더 힌트
- 허용 게임 루트
- session root 쓰기 가능 여부와 남은 공간

`probe_control=true`는 별도의 진단 Eden 프로세스를 시작해 인증된 protocol 2 `ping`을
보낸 뒤 종료합니다. 시작 직후 endpoint가 늦게 나타나는 경우를 위해 최대 2회까지 bounded
retry하며, 각 시도의 `detail`/return code를 `control.attempts`에 남깁니다. MCP가 관리 중인
세션이 실행 중이면 이 active probe는 거부됩니다.

커스텀 포터블 설치의 정상 기준은 다음과 같습니다.

```text
overall = ok
control.available = true
control.protocol = 2
control.return_code = 0
```

키 또는 firmware가 아직 없으면 Eden이 endpoint 준비 단계까지 도달하지 못할 수 있습니다.
사용자 소유 키와 firmware를 먼저 설치한 뒤 active probe를 실행하십시오.

## 보안 및 데이터 안전

- endpoint는 `127.0.0.1`에만 bind합니다.
- 각 프로세스는 예측하기 어려운 랜덤 토큰을 받고 모든 JSONL 요청에서 인증합니다.
- `EDEN_MCP_PORT`와 `EDEN_MCP_TOKEN`이 모두 없으면 endpoint는 활성화되지 않습니다.
- 임의 shell 명령이나 범용 파일 API를 노출하지 않습니다.
- 서버는 자신이 시작한 PID만 종료하며 기존 사용자 실행 Eden에는 attach하지 않습니다.
- 게임과 update는 읽기 대상으로 검증되며 복사하거나 재배포하지 않습니다.
- 세션 생성 실패 시 그 시도가 새로 만든 세션 폴더만 롤백합니다.
- 세션 root와 profile template이 서로를 포함하면 재귀 복사를 막기 위해 거부합니다.
- 세션 삭제는 canonical path가 설정된 session root 바로 아래인지 다시 확인합니다.
- 자동 정리는 유효한 MCP 소유권 매니페스트, 종료 상태, 비활성 PID를 모두 재검증하며 보호,
  legacy, 불명확 상태를 삭제하지 않습니다.
- patch staging은 정지된 관리 세션에만 새 고유 destination을 만들며 기존 patch를 덮어쓰지
  않습니다.
- artifact cleanup은 `.eden-mcp/diagnostics` 바로 아래의 생성 규칙 일치 항목만 삭제합니다.

자동화 종료는 응답과 로그를 flush한 뒤 성공 코드로 프로세스를 종료합니다. 반복 자동화에서
Eden의 일반 Qt teardown이 간헐적으로 hang 또는 access violation을 일으킨 사례를 피하기 위한
endpoint 전용 동작이며, 일반 Eden 실행에는 적용되지 않습니다.

## 현재 제약 사항

- 커스텀 endpoint는 아직 upstream Eden에 포함되지 않은 downstream 기능입니다.
- 공식 Eden 폴백은 Windows에서만 지원하며 창 focus와 occlusion의 영향을 받습니다.
- update는 session-only 등록이 아니라 선택한 프로필 NAND에 설치됩니다.
- analog stick과 임의 DLC mount는 현재 control protocol에 없습니다.
- 버튼 chord는 순서가 보장된 down/up 묶음이지만 emulator 내부의 단일 atomic packet은
  아닙니다.
- filename title ID 검사는 복호화된 NCA metadata의 대체물이 아닙니다.
- JPEG/resize screenshot은 지원하지 않으며 현재 native PNG를 사용합니다.
- 포터블 번들의 MCP 실행에는 Python 3.11+와 `uv`가 필요합니다.

## 소스에서 개발 및 실행

```powershell
git clone https://github.com/Leuconoe/eden-mcp.git
cd eden-mcp
uv sync --extra dev
uv run pytest -q
uv run ruff check .
uv run eden-mcp
```

Python wheel과 source distribution 생성:

```powershell
uv build
```

control-enabled Eden은 전체 업스트림 히스토리를 보존한
[`Leuconoe/eden`](https://github.com/Leuconoe/eden) 저장소에서 빌드할 수 있습니다.

```powershell
git clone --branch eden-mcp-control https://github.com/Leuconoe/eden.git
cd eden
cmake --build build-mcp-clang --target eden.exe --parallel 8
```

endpoint는 Qt frontend에만 추가되어 있으며 ordinary launch에서는 비활성 상태입니다. 빌드와
소스는 Eden의 GPL-3.0-or-later 조건을 따릅니다.

## 검증 상태

현재 소스의 MCP 도구 등록 수는 28개이며, v0.5.0 안정 릴리즈 자산에 모두 포함됩니다.
Python 테스트/Ruff 및 실제 Eden 런타임 결과는 실행 환경과
커스텀 control-enabled 바이너리의 조합에 따라 달라지므로, 릴리즈 전에 별도 검증 로그와
커밋/바이너리 hash를 함께 기록해야 합니다. 이 문서는 검증하지 않은 테스트를 통과했다고
주장하지 않습니다.

## 라이선스

- `eden-mcp`: [MIT](LICENSE)
- Eden 및 배포 `eden.exe`: GPL-3.0-or-later

이 프로젝트는 emulator automation 도구만 제공합니다. console key, firmware, NAND, 게임,
update, save 또는 screenshot 데이터는 사용자가 직접 관리해야 하며 이 저장소에서 배포하지
않습니다.

TDQS

A3.5/5.0

Scored across 27 tools

Disambiguation4/5

Most tools have distinct purposes, but the cluster of diagnostics tools (eden_get_logs, eden_diagnose_game_issue, eden_export_diagnostics, eden_get_runtime_metrics, eden_capture_sequence) overlaps in intent and could confuse agents choosing between log reading, issue diagnosis, and metrics sampling.

Naming Consistency5/5

All tool names follow a consistent eden_<verb>_<noun> pattern with snake_case, and verbs are chosen to reflect the action (get, list, start, stop, press, take, etc.). The few short forms like eden_stop and eden_status are clear exceptions that don't break the overall convention.

Tool Count2/5

At 27 tools, the surface is too large for the apparent scope. Many diagnostic and cleanup tools (e.g., eden_export_diagnostics, eden_list_artifacts, eden_cleanup_artifacts) are highly specialized and could be consolidated, making the set feel bloated.

Completeness4/5

The toolset covers the main emulator lifecycle: sessions, launching, input, capture, diagnostics, and compatibility advice. Minor gaps exist, such as no direct configuration mutation or save management, but agents can work around these.

Maintenance

ActivitySlowing
ResponsivenessNo issues