refactor(core): MCP 역할별 접근 제어 + asyncio.Lock 추가
- ROLE_MCP_ACCESS: agent만 MCP 도구 접근, 나머지 역할은 제거 - _settings_lock: settings.json 쓰기~프로세스 시작 직렬화 - agent.md: 쉘 명령 금지, MCP 도구 강제 사용 지시 추가 - config.py: WORKSPACE_BASE_DIR 경로 수정 (Variet-Worker) - run_bot.bat: conda 환경 variet-agent로 변경 - workspaces.json: orphan 정리 + 경로 수정 - known-issues: MCP 접근제어, yolo 자율성, Nyaa 카테고리 이슈 추가 - devlog: 002 entry 및 index 업데이트
This commit is contained in:
@@ -63,3 +63,21 @@
|
||||
- **원인**: Gemini CLI는 `cwd` 기준으로 `.gemini/settings.json`을 탐색. cwd가 다른 워크스페이스면 MCP 설정 못 찾음
|
||||
- **해결**: `~/.gemini/settings.json` (홈 레벨)에 mcpServers 등록. `_set_thinking_budget`에서 자동 관리
|
||||
- **주의**: MCP 서버 설정은 반드시 홈 레벨 settings.json에 등록. 프로젝트 레벨은 불충분
|
||||
|
||||
### [2026-03-12] MCP 역할별 접근 제어 — 모든 역할이 MCP 도구 접근
|
||||
- **증상**: coder, reviewer 등 텍스트 전용 역할도 anime/infra MCP 도구에 접근 가능
|
||||
- **원인**: `_set_thinking_budget()`이 역할 무관하게 모든 MCP 서버를 settings.json에 등록
|
||||
- **해결**: `ROLE_MCP_ACCESS` dict 추가, agent만 MCP 등록, 나머지는 제거. `asyncio.Lock` 추가로 settings.json 레이스 방지
|
||||
- **주의**: settings.json은 글로벌 파일이므로, 역할 전환 시 반드시 이전 설정을 정리해야 함
|
||||
|
||||
### [2026-03-12] Gemini CLI yolo — 에이전트 자율성 위험
|
||||
- **증상**: 애니 다운로드 요청 시 에이전트가 음악/만화를 다운로드하고 엉뚱한 폴더 생성
|
||||
- **원인**: `--approval-mode yolo`는 MCP 도구 + 쉘 + 파일 조작 모두 무승인 허용. 프롬프트 제한은 강제력 없음
|
||||
- **해결 (검토중)**: MCP 대신 Python 도구를 소스코드로 직접 제공하여 Gemini CLI가 읽고 사용하는 방식 검토
|
||||
- **주의**: 프롬프트는 "부탁"이지 "강제"가 아님. 안전장치는 코드(도구) 레벨에 구현해야 함
|
||||
|
||||
### [2026-03-12] Nyaa 검색 — anime 카테고리 미지정
|
||||
- **증상**: 애니 검색 시 음악, 만화, 라이트노벨 등 무관한 토렌트가 다운로드됨
|
||||
- **원인**: `NyaaClient.search()` 기본 category가 `0_0`(전체). Music, Manga 등 포함
|
||||
- **해결 (예정)**: 기본 category를 `1_2`(Anime English) 또는 `1_0`(Anime 전체)로 변경
|
||||
- **주의**: 외부 검색 API 사용 시 반드시 카테고리/필터를 명시적으로 지정
|
||||
|
||||
@@ -47,7 +47,7 @@ VIKUNJA_PROJECT_ID: int = int(os.getenv("VIKUNJA_PROJECT_ID", "7"))
|
||||
|
||||
# === Workspace ===
|
||||
WORKSPACE_BASE_DIR: str = os.getenv(
|
||||
"WORKSPACE_BASE_DIR", r"c:\Users\Certes\Desktop\VW_Proj"
|
||||
"WORKSPACE_BASE_DIR", r"c:\Users\Variet-Worker\Desktop\VW_Proj"
|
||||
)
|
||||
|
||||
# === qBittorrent ===
|
||||
|
||||
@@ -29,9 +29,23 @@ ROLE_THINKING: dict[str, int] = {
|
||||
}
|
||||
DEFAULT_THINKING = 4096
|
||||
|
||||
# 역할별 MCP 서버 접근 허용 목록
|
||||
# agent만 외부 도구 사용 가능, 나머지 역할은 텍스트 전용
|
||||
ROLE_MCP_ACCESS: dict[str, list[str]] = {
|
||||
"agent": ["anime", "infra"],
|
||||
"coder": [],
|
||||
"planner": [],
|
||||
"reviewer": [],
|
||||
"summarizer": [],
|
||||
"unified": [],
|
||||
}
|
||||
|
||||
# 동시 호출 제한 (Gemini AI Ultra 120RPM 고려)
|
||||
_semaphore = asyncio.Semaphore(4)
|
||||
|
||||
# settings.json 쓰기 경합 방지 (settings 쓰기 ~ 프로세스 시작 구간 직렬화)
|
||||
_settings_lock = asyncio.Lock()
|
||||
|
||||
# Windows에서 PS ExecutionPolicy 우회
|
||||
_IS_WIN = sys.platform == "win32"
|
||||
|
||||
@@ -76,8 +90,13 @@ class GeminiCaller:
|
||||
}
|
||||
|
||||
def _set_thinking_budget(self, role: str):
|
||||
"""역할별 thinkingBudget + MCP 서버 설정을 settings.json에 반영."""
|
||||
"""역할별 thinkingBudget + MCP 서버 설정을 settings.json에 반영.
|
||||
|
||||
MCP 서버는 ROLE_MCP_ACCESS에 따라 역할별로 필터링됩니다.
|
||||
agent 역할만 MCP 도구에 접근 가능하고, 나머지 역할은 제거됩니다.
|
||||
"""
|
||||
budget = ROLE_THINKING.get(role, DEFAULT_THINKING)
|
||||
allowed_mcp = ROLE_MCP_ACCESS.get(role, [])
|
||||
try:
|
||||
if _SETTINGS_PATH.exists():
|
||||
settings = json.loads(_SETTINGS_PATH.read_text(encoding="utf-8"))
|
||||
@@ -91,16 +110,22 @@ class GeminiCaller:
|
||||
thinking = default.setdefault("thinkingConfig", {})
|
||||
thinking["thinkingBudget"] = budget
|
||||
|
||||
# MCP 서버 (홈 레벨에 등록 — cwd와 무관하게 항상 사용 가능)
|
||||
# MCP 서버 — 역할별 접근 제어
|
||||
mcp_servers = settings.setdefault("mcpServers", {})
|
||||
for name, config in self._MCP_SERVERS.items():
|
||||
mcp_servers[name] = config
|
||||
for name, cfg in self._MCP_SERVERS.items():
|
||||
if name in allowed_mcp:
|
||||
mcp_servers[name] = cfg
|
||||
else:
|
||||
mcp_servers.pop(name, None)
|
||||
|
||||
_SETTINGS_PATH.write_text(
|
||||
json.dumps(settings, indent=2, ensure_ascii=False),
|
||||
encoding="utf-8",
|
||||
)
|
||||
logger.debug(f"thinkingBudget={budget} for role={role}")
|
||||
logger.debug(
|
||||
f"settings.json 업데이트: role={role}, budget={budget}, "
|
||||
f"mcp={allowed_mcp or 'none'}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"settings.json 업데이트 실패 (role={role}): {e}")
|
||||
|
||||
@@ -132,7 +157,6 @@ class GeminiCaller:
|
||||
|
||||
async def _call_text(self, role: str, context: str, timeout: int) -> str:
|
||||
"""텍스트 전용 호출."""
|
||||
self._set_thinking_budget(role)
|
||||
prompt_file = ROLE_PROMPTS_DIR / f"{role}.md"
|
||||
if prompt_file.exists():
|
||||
system_prompt = prompt_file.read_text(encoding="utf-8")
|
||||
@@ -147,6 +171,9 @@ class GeminiCaller:
|
||||
)
|
||||
|
||||
try:
|
||||
# Lock: settings.json 쓰기 ~ 프로세스 시작 직렬화
|
||||
async with _settings_lock:
|
||||
self._set_thinking_budget(role)
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*self._build_cmd(),
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
@@ -207,7 +234,6 @@ class GeminiCaller:
|
||||
self, role: str, context: str, cwd: str, timeout: int,
|
||||
) -> str:
|
||||
"""에이전트 모드 구현."""
|
||||
self._set_thinking_budget(role)
|
||||
prompt_file = ROLE_PROMPTS_DIR / f"{role}.md"
|
||||
if prompt_file.exists():
|
||||
system_prompt = prompt_file.read_text(encoding="utf-8")
|
||||
@@ -241,6 +267,9 @@ class GeminiCaller:
|
||||
)
|
||||
|
||||
try:
|
||||
# Lock: settings.json 쓰기 ~ 프로세스 시작 직렬화
|
||||
async with _settings_lock:
|
||||
self._set_thinking_budget(role)
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
*self._build_cmd(),
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
|
||||
@@ -3,3 +3,6 @@
|
||||
| # | 시간 | 작업 | 커밋 | 상태 |
|
||||
|---|------|------|------|------|
|
||||
| 001 | 16:45 | MCP 기반 에이전트 아키텍처 재설계 — unified.md 분류기 → Gemini CLI + MCP 자율 에이전트 전환 | `246d2a2` | ✅ |
|
||||
| 002 | 18:30 | MCP 역할별 접근 제어 + asyncio.Lock 추가, run_bot.bat 환경 수정, 아키텍처 정밀 검토 | - | 🔧 |
|
||||
| 003 | 19:30 | 봇 실행 테스트 — fastapi 설치, workspace 경로 수정, 에이전트 자율성 문제 발견 | - | 🔧 |
|
||||
| 004 | 20:00 | 아키텍처 재분석 — MCP vs 구조화 파이프라인 vs Python 도구 직접 제공 방식 비교 검토 | - | 🔧 |
|
||||
|
||||
24
docs/devlog/entries/20260312-002.md
Normal file
24
docs/devlog/entries/20260312-002.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# MCP 역할별 접근 제어 + 아키텍처 재분석
|
||||
|
||||
- **시간**: 2026-03-12 18:30~21:59
|
||||
- **Commit**: (이번 커밋에 포함)
|
||||
- **Vikunja**: 신규 태스크 생성 예정
|
||||
|
||||
## 결정 사항
|
||||
|
||||
### MCP 역할별 접근 제어 (구현 완료)
|
||||
- `ROLE_MCP_ACCESS` dict로 agent만 MCP 도구 접근 허용
|
||||
- `asyncio.Lock`으로 settings.json 쓰기~프로세스 시작 구간 직렬화
|
||||
- 나머지 역할(coder, reviewer 등)은 MCP 제거됨
|
||||
|
||||
### 아키텍처 방향성 (검토중)
|
||||
- **MCP 방식의 문제**: `--approval-mode yolo`에서 에이전트가 쉘/파일 조작 무제한 → 음악/만화 다운 등 예측 불가 동작
|
||||
- **검토 중인 대안**: MCP 대신 Python 도구를 소스코드로 직접 제공 → Gemini CLI가 읽고 이해하여 사용
|
||||
- **핵심 원칙**: "프롬프트는 부탁, 코드는 법률" → 안전장치는 도구 코드 레벨에 구현
|
||||
|
||||
## 미완료
|
||||
- [ ] 아키텍처 최종 결정 (MCP → Python 도구 직접 제공 방식 전환)
|
||||
- [ ] Nyaa 검색 카테고리 `1_2`로 변경
|
||||
- [ ] NAS 기존 폴더 매칭 로직 추가
|
||||
- [ ] discord_bot.py dead code ~572줄 정리
|
||||
- [ ] config.py .env 따옴표 처리
|
||||
@@ -10,26 +10,35 @@
|
||||
- 여러 도구를 **순서대로** 사용해야 할 때도 있습니다.
|
||||
- 도구 호출 결과가 불충분하면 **다른 도구를 시도**하거나 **다른 파라미터**로 재호출하세요.
|
||||
|
||||
## ⛔ 절대 금지
|
||||
|
||||
- **쉘 명령어로 직접 다운로드하지 마세요** (curl, wget, pip install 등)
|
||||
- **파일을 직접 생성/수정하지 마세요** — MCP 도구만 사용하세요
|
||||
- 사용자가 요청하지 않은 작업을 임의로 수행하지 마세요
|
||||
|
||||
## 사용 가능한 도구 영역
|
||||
|
||||
### 🎬 anime 서버
|
||||
### 🎬 anime 서버 — 애니메이션 관련은 반드시 이 도구만 사용
|
||||
- `anime_search` — 애니 검색 (제목, 자막, 토렌트)
|
||||
- `anime_download` — 애니 다운로드 (자막+영상)
|
||||
- `anime_download` — 애니 다운로드 (자막+영상). 한 번에 **하나의 작품**만 다운로드.
|
||||
- `anime_schedule` — 편성표 조회
|
||||
- `anime_download_status` — qBittorrent 상태
|
||||
- `anime_nas_list` — NAS 다운로드 목록
|
||||
|
||||
### 🔧 infra 서버
|
||||
### 🔧 infra 서버 — Git/태스크 관련은 반드시 이 도구만 사용
|
||||
- `gitea_commits`, `gitea_prs`, `gitea_issues`, `gitea_branches` — Git 관리
|
||||
- `vikunja_tasks`, `vikunja_create_task`, `vikunja_complete_task` — 태스크 관리
|
||||
|
||||
### 💻 내장 도구
|
||||
- 프로젝트 파일 읽기/쓰기, 쉘 명령 실행 등 Gemini CLI 내장 도구 사용 가능
|
||||
## 복수 작품 처리 방법
|
||||
|
||||
사용자가 "여러 작품 다운로드" 등 복수 작업을 요청하면:
|
||||
1. 먼저 `anime_nas_list`로 대상 목록을 확인하세요
|
||||
2. 각 작품마다 `anime_download`를 **개별 호출**하세요
|
||||
3. 진행 상황과 결과를 정리하여 보고하세요
|
||||
|
||||
## 응답 규칙
|
||||
|
||||
- **한국어**로 응답하세요.
|
||||
- 도구 실행 결과를 사용자에게 **알기 쉽게 정리**하세요.
|
||||
- 파일 변경 시 **변경 요약**을 제공하세요.
|
||||
- 에러 발생 시 **원인과 대안**을 안내하세요.
|
||||
- 불필요하게 길지 않게, **핵심만** 전달하세요.
|
||||
|
||||
@@ -7,7 +7,7 @@ echo Variet Agent 시작
|
||||
echo ==========================================
|
||||
echo.
|
||||
|
||||
C:\ProgramData\miniforge3\envs\agent_chat\python.exe main.py
|
||||
C:\ProgramData\miniforge3\envs\variet-agent\python.exe main.py
|
||||
|
||||
echo.
|
||||
echo 봇이 종료되었습니다. 아무 키나 누르면 창을 닫습니다.
|
||||
|
||||
@@ -1,58 +1,7 @@
|
||||
{
|
||||
"5608566207": {
|
||||
"name": "test_1_orphan_20260307",
|
||||
"path": "c:\\Users\\Certes\\Desktop\\VW_Proj\\test_1_orphan_20260307",
|
||||
"channel_id": 0,
|
||||
"git": {
|
||||
"url": "",
|
||||
"token": "",
|
||||
"repo": "",
|
||||
"branch": "main"
|
||||
},
|
||||
"vikunja": {
|
||||
"url": "",
|
||||
"token": "",
|
||||
"project_id": 0
|
||||
},
|
||||
"docs_path": "docs/wiki"
|
||||
},
|
||||
"8350378037": {
|
||||
"name": "test_2_orphan_20260307",
|
||||
"path": "c:\\Users\\Certes\\Desktop\\VW_Proj\\test_2_orphan_20260307",
|
||||
"channel_id": 0,
|
||||
"git": {
|
||||
"url": "",
|
||||
"token": "",
|
||||
"repo": "",
|
||||
"branch": "main"
|
||||
},
|
||||
"vikunja": {
|
||||
"url": "",
|
||||
"token": "",
|
||||
"project_id": 0
|
||||
},
|
||||
"docs_path": "docs/wiki"
|
||||
},
|
||||
"1479610776502403186": {
|
||||
"name": "test_1",
|
||||
"path": "c:\\Users\\Certes\\Desktop\\VW_Proj\\test_1",
|
||||
"channel_id": 1479610776502403186,
|
||||
"git": {
|
||||
"url": "",
|
||||
"token": "",
|
||||
"repo": "",
|
||||
"branch": "main"
|
||||
},
|
||||
"vikunja": {
|
||||
"url": "",
|
||||
"token": "",
|
||||
"project_id": 0
|
||||
},
|
||||
"docs_path": "docs/wiki"
|
||||
},
|
||||
"1480113683849023661": {
|
||||
"name": "variet-agent",
|
||||
"path": "c:\\Users\\Certes\\Desktop\\VW_Proj\\variet-agent",
|
||||
"path": "c:\\Users\\Variet-Worker\\Desktop\\VW_Proj\\variet-agent",
|
||||
"channel_id": 1480113683849023661,
|
||||
"git": {
|
||||
"url": "",
|
||||
|
||||
Reference in New Issue
Block a user