Post

2026-08-03 TIL (110일차)

2026-08-03 TIL (110일차)

언리얼 팀 프로젝트 세팅 — Monolith MCP 플러그인 & GitHub 협업 규칙

팀 프로젝트 초기 세팅에서 정리한 두 가지. 개발 환경(MCP 플러그인)협업 규칙(PR/브랜치) 입니다. 절차 자체보다 “왜 이 순서인가” 를 중심으로 남깁니다.


Part 1. Monolith MCP 플러그인 설치

1. 설치 절차 요약

단계작업확인 포인트
1프로젝트 루트(.uproject 위치)에 Plugins 폴더 생성 후 clone엔진 폴더가 아니라 프로젝트 루트
2폴더명을 monolithMonolith 로 변경설정 파일·문서 표기와 통일
3에디터·Rider 완전 종료 → .uproject 우클릭 → Generate Visual Studio project files종료하지 않으면 파일 잠김
4솔루션 빌드 (Development Editor / Win64)첫 빌드 461개 파일, 5~15분
5.mcp.jsonmonolith 블록 추가JSON 쉼표 문법
6에디터 실행 후 인덱싱 대기 (30~60초)Output Log 확인
7Claude Code에서 /mcpmonolith 체크 표시

최종 구조

1
2
3
4
5
6
7
8
9
프로젝트루트/
├── 프로젝트명.uproject
├── Source/
├── Content/
└── Plugins/
    └── Monolith/
        ├── Monolith.uplugin
        ├── Source/
        └── Binaries/

2. 왜 엔진이 아니라 프로젝트 플러그인인가

Monolith가 만드는 데이터가 전부 프로젝트 단위라는 게 핵심 이유입니다.

  • 생성 데이터: 프로젝트 에셋 인덱스(SQLite), 리플렉션 테이블, 프로젝트 플러그인 스캔 결과
  • 엔진 폴더에 설치해도 이 인덱스는 프로젝트마다 따로 생성되므로 엔진 설치의 이점이 없습니다.
  • .mcp.json프로젝트 루트에 있어야 하므로 위치상으로도 자연스럽습니다.
  • 엔진 폴더는 Program Files 하위라 쓰기 권한 문제가 잦고, 엔진을 재설치하면 플러그인이 사라집니다.

정리: 데이터의 생명주기가 프로젝트에 묶여 있으면 설치 위치도 프로젝트여야 합니다. 앞서 정리한 RAII의 “자원 수명을 소유자에 묶는다” 는 사고와 같은 결입니다.

3. 포트와 로그 — 검증 방법

서버포트
unreal-mcp (Epic 내장)8000
monolith9316

포트가 다르므로 두 서버를 동시에 사용할 수 있습니다. .mcp.jsonunreal-mcp 블록 뒤에 쉼표를 붙이고 monolith 블록을 추가합니다.

1
2
3
4
5
6
7
{
	"mcpServers":
	{
		"unreal-mcp": { "type": "http", "url": "http://127.0.0.1:8000/mcp" },
		"monolith":   { "type": "http", "url": "http://127.0.0.1:9316/mcp" }
	}
}

로그 확인 — 카테고리가 두 개로 나뉘어 있다는 점이 함정

카테고리확인할 문구의미
LogMonolithMonolith MCP server listening on port 9316서버 시작
LogMonolithIndexFull index complete (542 assets indexed, 0 errors)인덱싱 완료

필터에 LogMonolith만 넣으면 인덱스 로그가 안 잡힐 수 있습니다. 서버 시작 로그가 안 보이면 필터를 Monolith로 넓혀 전체를 확인합니다.

최종 동작 테스트: Claude Code에서 /mcpmonolith 체크 확인 → “monolith가 지원하는 네임스페이스 목록을 가져와줘”blueprint, material, animation, niagara 등이 반환되면 정상입니다.

4. 막히기 쉬운 지점

증상원인 / 해결
빌드 전체 실패기존 프로젝트에 컴파일 에러가 하나라도 있으면 빌드가 전부 실패합니다. 기존 에러부터 해결
빌드가 멈춘 것처럼 보임461개 파일 컴파일 중. 5~15분은 정상
DLL이 안 생김Plugins\Monolith\Binaries\Win64\UnrealEditor-Monolith*.dll 생성 여부로 판단
MCP 전체가 로드 안 됨.mcp.json 쉼표 누락 → JSON 문법 오류로 전체가 죽습니다
Rider에서 .mcp.json이 안 보임좌측 상단 Solution → File System 으로 전환
폴더명이 소문자윈도우는 대소문자를 구분하지 않아 당장은 되지만, 나중에 Git·Linux 환경에서 문제가 됩니다

Part 2. GitHub PR / 협업 규칙 (Free + private 기준)

5. PR 제목 = main의 커밋 메시지

이번 세팅의 가장 중요한 규칙입니다.

Squash 머지에서는 PR 제목이 그대로 main의 커밋 메시지가 됩니다.

대상신경 쓸 정도
브랜치 안의 커밋 메시지대충 써도 무관 (main에 안 올라감)
PR 제목여기만 신경 쓰면 됨

⚠️ PR 생성 화면에서 자동으로 채워지는 제목은 커밋 메시지 또는 브랜치 이름입니다. 반드시 [타입] 내용 형식으로 고쳐야 합니다.

타입 목록

타입용도
feat기존 파일에 새 기능 추가
add없던 파일 생성, 초기 세팅
fix버그 수정
refactor리팩토링
asset에셋 추가·수정 (.uasset, .umap, 텍스처, 사운드)
move / delete파일 이동·정리 / 삭제
test테스트 코드
gitfix.gitignore, .gitattributes, .editorconfig
script.Build.cs, .uproject, Config/*.ini
chore주석, 변수·클래스명 수정
improve개선

6. 타입은 반드시 하나만

[add/fix]처럼 병기하면 안 되는 이유:

문제설명
검색이 깨짐git log --grep="^\[fix\]"[add/fix]를 못 잡음
타입 의미 상실한 단어로 성격을 알려주는 게 타입인데, 2개면 본문을 읽어야 함
미끄러짐한 번 허용하면 [feat/fix/refactor/chore]까지 늘어남
자동화 불가릴리즈 노트 생성, 제목 검증 액션이 전부 걸림

여러 성격이 섞였다면 대표 타입 하나(feat > fix > refactor > chore 우선순위)를 고르고 나머지는 본문에 씁니다.

1
2
3
4
5
[feat] 플레이어 이동 및 카메라 컨트롤 구현

- 이동 입력 처리 (WASD, 대시)
- 스프링암 카메라 추가
- 회전값 오버플로 버그 수정

타입 하나로 표현이 안 될 정도로 크다면, 먼저 PR을 나누는 것을 고민해야 합니다.

CLI 커밋 시: 제목과 본문 사이 빈 줄이 필수입니다. 없으면 전체가 한 덩어리 제목이 되어 git log --oneline이 지저분해집니다. (PR 화면의 squash는 제목/본문 칸이 분리되어 있어 이 문제가 없습니다.)

7. Free + private의 한계 — 무엇이 되고 무엇이 안 되는가

Ruleset(브랜치 보호)은 private 저장소에서 유료 플랜(Pro / Team)이 필요합니다. 현재는 Settings → General → Pull Requests 설정만으로 운영합니다.

설정
Allow merge commits❌ 해제
Allow squash merging✅ 체크
Default commit messagePull request title and description
Allow rebase merging❌ 해제
Automatically delete head branches✅ 체크

✅ 서버가 보장하는 것

  • Squash 강제 — 머지 화면에 Squash and merge 버튼만 뜨고 UI·API 모두 우회 불가
  • 커밋 메시지 자동 채움 — PR 제목 → 커밋 제목, PR 본문 → 커밋 본문
  • 리모트 브랜치 자동 삭제, main 히스토리 선형 유지(1 PR = 1 커밋)
  • 머지 방식 제한은 모든 브랜치에 적용됩니다. (룰셋은 지정 브랜치만) → 이 항목은 룰셋을 못 써도 손실이 없습니다.

❌ 서버가 못 막는 것

항목실제 영향
Require a pull request before mergingmain 직접 push 가능 → ⚠️ 유일한 실질 구멍
Required approvals / 승인 계열본인 PR 혼자 머지 가능 → 팀 약속으로 대체
Require conversation resolution미해결 코멘트 상태로 머지 가능
Block force pushes--force 차단 안 됨 (rebase 안 하면 무관, 복구 시엔 오히려 유리)

룰셋 13개 항목 중 실제로 아쉬운 건 리뷰 강제 계열이고, 그중 핵심은 단 하나 — main 직접 push 차단입니다. 결과물은 룰셋이 있을 때와 동일하게 만들 수 있지만, 지키는 건 사람 몫입니다.

팀 규칙 5줄

  1. main에 직접 push 금지 — 무조건 브랜치 → PR
  2. Approve 1개 받고 머지 (서버가 안 막으므로 각자 지킬 것)
  3. Git Fork의 Force push 체크박스 사용 금지
  4. 머지 후 로컬 브랜치 삭제하고 main에서 새로 분기
  5. PR 제목은 [타입] 내용 형식

public으로 바꾸면 안 되는 이유: 유료 에셋(Fab)이 포함된 저장소를 공개하면 원본 .uasset 재배포에 해당해 라이선스 위반입니다. DMCA → 저장소 삭제, 계정 정지로 이어지고 같은 계정의 GitHub Pages 블로그까지 함께 죽습니다. → private 유지가 정답.

8. pre-push 훅으로 유일한 구멍 메우기

서버가 못 막으니 클라이언트에서 막습니다. Git Fork도 훅을 그대로 실행합니다.

.githooks/pre-push

1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh
while read local_ref local_sha remote_ref remote_sha
do
  case "$remote_ref" in
    refs/heads/main)
      echo "main에 직접 push 금지. 브랜치 -> PR로 올리세요."
      exit 1
      ;;
  esac
done
exit 0
1
2
3
4
5
6
# 커밋할 때 (윈도우에서도 실행되도록 실행 권한 부여)
git update-index --chmod=+x .githooks/pre-push

# 팀원 각자 1회 실행
git config core.hooksPath .githooks
git config alias.pushf "push --force-with-lease"
  • --no-verify로 우회 가능하지만, 사고는 대부분 실수라 이것만으로 대부분 걸러집니다.
  • ⚠️ 이 훅을 켜면 본인도 main에 직접 push 못 합니다. → 초기 세팅 커밋을 모두 올린 뒤 맨 마지막에 추가합니다.
  • 추가 안전장치: 팀원 1명이 주 1회 git clone --mirror로 미러 백업을 들고 있으면 force push 사고까지 커버됩니다.

9. 초기 세팅 커밋 순서 — LFS를 가장 먼저

순서커밋비고
1[gitfix] 언리얼 프로젝트용 .gitignore, .gitattributes 초기 세팅⚠️ LFS를 가장 먼저
2[add] .editorconfig 추가한글 주석 깨짐 방지
3[script] 엔진 버전 업데이트 
4[script] 프로젝트 플러그인 추가 
5[add] pre-push 훅 추가맨 마지막

LFS를 먼저 걸어야 하는 이유: .uasset / .umap을 일반 파일로 한 번 커밋하면 그 blob이 히스토리에 박힙니다. 나중에 LFS를 걸어도 이미 올라간 건 옮겨지지 않습니다. 되돌리려면 git filter-repo로 히스토리를 재작성해야 하고 팀 전원 재클론이 필요합니다.

순서가 성능이 아니라 되돌릴 수 있는지 여부를 결정하는 사례입니다. 되돌리기 비용이 큰 작업을 항상 앞에 둡니다.

.editorconfig 주의점 3가지

주의이유
[*]utf-8-bom 금지YAML/JSON/셸 스크립트에 BOM이 붙으면 파서가 깨짐 → C++ 파일에만 적용
[*]indent_style = tab 주의YAML은 탭을 문법적으로 금지 → Actions 워크플로 무효
end_of_line 미지정.gitattributeseol 설정과 충돌 → 개행은 gitattributes에서만 관리

.editorconfig새로 저장하는 파일에만 적용되므로 초기 단계에 넣는 것이 타이밍상 최적입니다.

10. Git Fork 사용 시 주의점

  • 자동 삭제는 리모트만 — 로컬 브랜치와 origin/... 추적 참조는 남습니다.
    1
    2
    
    git fetch --prune                 # 없어진 origin 참조 정리
    git branch -d feature/이름/기능명   # 로컬 브랜치 삭제
    

    Fork → Preferences → Git → “Prune tracking branches on fetch” 체크로 자동화.

  • Squash 머지 후 그 브랜치 재사용 금지 — Squash는 새 커밋을 만들기 때문에 옛 브랜치에서 계속 작업하면 이미 반영된 변경이 중복으로 딸려와 유령 충돌이 납니다. → 머지 완료 → 로컬 브랜치 삭제 → main pull → 새 브랜치 생성
  • 브랜치 확인 습관 — 사고의 대부분은 main을 체크아웃해둔 채 작업하다 발생합니다.

11. 실수로 main에 직접 push한 경우

General 설정은 PR을 머지할 때만 적용됩니다. 로컬 merge → push는 그 경로를 거치지 않습니다.

 PR로 머지로컬 merge 후 push
main에 남는 커밋1개 (Squash)wip 커밋 전부 + 머지 커밋
커밋 메시지PR 제목/본문wip: ... 그대로
히스토리일직선갈라졌다 합쳐진 모양

이미 main에 들어갔으므로 PR로 다시 올릴 수 없습니다 (There isn't anything to compare).

① 아직 아무도 pull 안 했으면 (권장)

1
2
3
4
git log --oneline origin/main          # 푸시 직전 커밋 해시 확인
git switch main
git reset --hard <해시>
git push --force-with-lease origin main

Block force pushes가 꺼져 있어 이 복구가 가능합니다. (룰셋이 걸려 있으면 오히려 막힙니다.)

② 이미 팀원이 pull했으면 — force push하면 팀원 로컬이 깨지므로 revert로 처리합니다.

1
2
3
4
git switch main
git pull
git revert -m 1 <머지커밋해시>
git push origin main

⚠️ 코드는 원복되지만 히스토리가 지저분해지고, 그 브랜치를 나중에 PR로 올릴 때 revert된 상태라 변경이 안 잡힙니다. 브랜치를 새로 만들어 작업물을 옮겨야 합니다.

발견이 빠를수록 압도적으로 쉽습니다. 실수로 올렸으면 즉시 팀에 알릴 것. pull 전이면 5분이면 정리됩니다.

12. 유료 전환 시 Ruleset (참고)

Organization + Team 플랜(약 $4/인·월) 전환 시 적용할 설정입니다. 언리얼 프로젝트는 LFS 무료 1GB를 금방 초과하므로, 대역폭 문제와 함께 해결하는 시점이 자연스럽습니다.

Settings → Rules → Rulesets → New branch ruleset

항목
Enforcement statusActive
Bypass list비움 (관리자도 예외 없음)
Target branchesInclude default branch (main)
Restrict deletions / Require linear history
Require a pull request before merging✅ (Required approvals 1)
Require approval of the most recent push
Require conversation resolution
Allowed merge methodsSquash
Block force pushes

Required approvals 특성

항목내용
작성자는 카운트 불가본인 PR엔 Approve 버튼이 안 뜸 (GitHub 구조)
“지정 리뷰어”가 아니라 “아무나”A를 지정해도 B가 승인하면 충족. 지정은 알림일 뿐
Comment는 카운트 안 됨Approve만 인정
Request changes숫자와 무관하게 차단, 건 사람이 직접 풀어줘야 함

⚠️ Required approvals = 1 + Bypass list 비움 상태에서는 혼자서 아무것도 main에 올릴 수 없습니다. → 초기 세팅을 모두 끝낸 뒤 마지막에 Active로 전환합니다. (pre-push 훅과 같은 원리)

부록 — Default commit message 옵션 비교

옵션main 커밋 본문에 남는 것
Default messagePR 제목 + 커밋 목록이 섞임
Pull request title제목만 (가장 간결)
Pull request title and commit details자잘한 커밋 로그 (wip: ...) — PR 본문 무시
Pull request title and descriptionPR 본문 — 개별 커밋 로그 무시

title and description을 쓰는 이유는 Git Fork에서 main 히스토리를 볼 때 정성껏 쓴 요약이 보이기 때문입니다. 사라진 개별 커밋 로그도 GitHub의 완료된 PR 페이지에 남아 언제든 추적 가능합니다.


오늘의 정리

배운 것내용
설치 위치는 데이터 수명이 결정한다Monolith의 인덱스가 프로젝트 단위이므로 엔진이 아니라 프로젝트 플러그인
되돌리기 비용이 큰 작업을 먼저LFS를 먼저 걸어야 하는 이유. 히스토리에 박힌 blob은 전원 재클론 없이 못 뺀다
차단 장치는 맨 마지막에 켠다pre-push 훅, Ruleset Active 전환 모두 초기 세팅 후에
PR 제목이 곧 영구 기록Squash에서 PR 제목 = main 커밋 메시지. 자동 채워진 제목은 반드시 고칠 것
서버가 못 막는 건 사람이 지킨다Free + private에서 실질 구멍은 “main 직접 push” 하나. 훅 + 팀 약속으로 커버
사고는 발견 속도가 전부pull 전이면 reset --hard + --force-with-lease로 5분, 후면 revert로 복잡해진다
This post is licensed under CC BY 4.0 by the author.