# Nexus Agent Backend

FastAPI 기반 백엔드. MCP 서버 관리, Agent Skills 관리, Custom Pages 폴더 트리, Workspace 파일 관리, 장기 기억(Memory), SSE 실시간 스트리밍, LLM 설정 및 도구 호출 오케스트레이션을 담당합니다.

## 실행

```bash
cp .env.example .env  # GOOGLE_API_KEY, LITELLM_MODEL 설정
uv sync
uv run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```

## 환경 변수

`backend/.env` 파일에 설정 (`backend/.env.example` 참고):

| 변수 | 필수 | 기본값 | 설명 |
|------|------|--------|------|
| `GOOGLE_API_KEY` | O | — | Gemini API 키 |
| `LITELLM_MODEL` | O | `gemini/gemini-3-flash-preview` | LLM 모델 이름 |
| `LITELLM_BASE_URL` | X | — | 커스텀 LLM 엔드포인트 |

## 구조

```
app/
├── main.py                      # FastAPI 앱, lifespan, CORS, 라우터 등록
├── core/
│   ├── llm.py                   # LLMClient (LiteLLM 래퍼, 동적 설정 로드)
│   ├── agent.py                 # AgentOrchestrator (시스템 프롬프트 + 도구 라우팅 + SSE 스트리밍)
│   ├── mcp_manager.py           # MCPClientManager (MCP 서버 연결 관리)
│   ├── skill_manager.py         # SkillManager (스킬 탐색/로드/실행)
│   ├── page_manager.py          # PageManager (페이지/폴더/북마크 트리 관리)
│   ├── settings_manager.py      # SettingsManager (LLM 설정 관리)
│   ├── workspace_manager.py     # WorkspaceManager (워크스페이스 CRUD + 파일 읽기/쓰기/편집)
│   ├── workspace_tools.py       # LLM용 워크스페이스 도구 7종 + 안전장치
│   └── memory_manager.py        # 장기 기억 자동 추출/관리 (LLM 기반)
├── api/endpoints/
│   ├── chat.py                  # POST /api/chat/ + POST /api/chat/stream (SSE)
│   ├── mcp.py                   # MCP 서버/도구 CRUD API
│   ├── skills.py                # Agent Skills CRUD API
│   ├── pages.py                 # Pages/폴더/북마크 CRUD API
│   ├── settings.py              # LLM 설정, 메모리 설정 및 헬스체크 API
│   ├── workspace.py             # Workspace CRUD + 파일 트리/내용 API
│   └── memory.py                # 장기 기억 CRUD API
└── models/
    ├── mcp.py                   # MCPServerConfig, MCPServerInfo, MCPToolInfo
    ├── skill.py                 # SkillInfo, SkillDetail, CreateSkillRequest
    ├── page.py                  # PageInfo, CreateFolderRequest, CreateBookmarkRequest
    ├── settings.py              # LLMSettings, AppSettings, UpdateLLMRequest
    ├── workspace.py             # WorkspaceInfo, FileTreeNode, FileContent, Request 모델
    └── memory.py                # MemoryItem, MemoryCategory, MemorySettings
```

### 설정 파일

| 파일 | 설명 |
|------|------|
| `.env` | 환경 변수 (API 키, 모델 이름) |
| `mcp.json` | MCP 서버 연결 설정 (자동 생성) |
| `skills.json` | 스킬 비활성화 상태 관리 (자동 생성) |
| `pages.json` | 페이지 메타데이터 및 트리 구조 (자동 생성) |
| `settings.json` | LLM 설정 — 모델, 온도, 시스템 프롬프트 등 (자동 생성) |
| `workspaces.json` | 워크스페이스 등록 정보 및 활성 상태 (자동 생성) |
| `memories.json` | 장기 기억 데이터 (자동 생성) |

### 데이터 디렉토리

| 디렉토리 | 설명 |
|----------|------|
| `skills/` | Agent Skills 저장 (SKILL.md + scripts/ + references/) |
| `pages/` | 업로드된 HTML 파일 저장 |

## API 엔드포인트

### Chat

| 메서드 | 경로 | 설명 |
|--------|------|------|
| POST | `/api/chat/` | 메시지 전송 및 AI 응답 (JSON) |
| POST | `/api/chat/stream` | SSE 실시간 스트리밍 (thinking/tool_call/tool_result/content 이벤트) |

### MCP 서버 관리

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/mcp/servers` | 전체 서버 목록 + 상태 |
| GET | `/api/mcp/servers/{name}` | 특정 서버 상세 |
| POST | `/api/mcp/servers` | 서버 추가 + 연결 |
| PATCH | `/api/mcp/servers/{name}` | 서버 설정 수정 |
| DELETE | `/api/mcp/servers/{name}` | 서버 삭제 |
| POST | `/api/mcp/servers/{name}/connect` | 수동 연결 |
| POST | `/api/mcp/servers/{name}/disconnect` | 수동 연결 해제 |
| POST | `/api/mcp/servers/{name}/restart` | 재시작 |

### MCP 도구

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/mcp/tools` | 전체 도구 목록 |
| GET | `/api/mcp/tools/{server_name}` | 특정 서버 도구 목록 |

### MCP 설정

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/mcp/config` | mcp.json 조회 |
| PUT | `/api/mcp/config` | mcp.json 전체 덮어쓰기 |
| POST | `/api/mcp/reload` | 설정 재로드 |

### Agent Skills 관리

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/skills/` | 전체 스킬 목록 |
| GET | `/api/skills/{name}` | 스킬 상세 (SKILL.md 전체 내용 포함) |
| POST | `/api/skills/` | 스킬 생성 |
| PATCH | `/api/skills/{name}` | 스킬 업데이트 (enabled, description, instructions) |
| DELETE | `/api/skills/{name}` | 스킬 삭제 |
| POST | `/api/skills/{name}/execute` | 스킬 스크립트 실행 |
| POST | `/api/skills/reload` | 스킬 디렉토리 재스캔 |
| POST | `/api/skills/upload` | ZIP 파일로 스킬 업로드 |
| POST | `/api/skills/import` | 로컬 경로에서 스킬 임포트 |

### Custom Pages 관리

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/pages/` | 페이지 목록 (parent_id 쿼리로 하위 필터) |
| GET | `/api/pages/{page_id}` | 페이지 상세 정보 |
| GET | `/api/pages/{page_id}/content` | HTML 콘텐츠 또는 URL 리다이렉트 |
| POST | `/api/pages/upload` | HTML 파일 업로드 |
| POST | `/api/pages/import` | 로컬 경로에서 HTML 임포트 |
| POST | `/api/pages/bookmark` | URL 북마크 생성 |
| POST | `/api/pages/folders` | 폴더 생성 |
| POST | `/api/pages/check-frameable/{page_id}` | URL iframe 지원 여부 확인 |
| GET | `/api/pages/breadcrumb/{page_id}` | 브레드크럼 네비게이션 경로 |
| PATCH | `/api/pages/{page_id}` | 페이지 메타데이터 수정 |
| DELETE | `/api/pages/{page_id}` | 페이지 삭제 (폴더는 재귀 삭제) |

### Workspace 관리

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/workspace/` | 전체 워크스페이스 목록 |
| POST | `/api/workspace/` | 워크스페이스 등록 (디렉토리 존재 검증) |
| GET | `/api/workspace/{id}` | 워크스페이스 상세 정보 |
| PATCH | `/api/workspace/{id}` | 이름/설명 수정 |
| DELETE | `/api/workspace/{id}` | 워크스페이스 삭제 |
| POST | `/api/workspace/{id}/activate` | 활성화 (하나만 활성 가능) |
| POST | `/api/workspace/deactivate` | 모든 워크스페이스 비활성화 |
| GET | `/api/workspace/{id}/tree` | 파일 트리 (query: path, max_depth) |
| GET | `/api/workspace/{id}/file` | 파일 내용 (query: path, offset, limit) |
| POST | `/api/workspace/{id}/file` | 파일 쓰기 (body: path, content) |
| POST | `/api/workspace/{id}/edit` | 파일 편집 (body: path, old_string, new_string, replace_all) |

### Settings

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/settings/health` | 서버 헬스체크 |
| GET | `/api/settings/llm` | LLM 설정 조회 (API 키 마스킹) |
| PATCH | `/api/settings/llm` | LLM 설정 업데이트 |
| GET | `/api/settings/memory` | 메모리 설정 조회 |
| PATCH | `/api/settings/memory` | 메모리 설정 업데이트 (enabled 토글) |

### Memory (장기 기억)

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/memory/` | 전체 기억 목록 |
| POST | `/api/memory/` | 기억 수동 추가 |
| PATCH | `/api/memory/{memory_id}` | 기억 수정 (content, category) |
| DELETE | `/api/memory/{memory_id}` | 기억 삭제 |
| DELETE | `/api/memory/` | 전체 기억 삭제 |

> 대화 완료 시 `MemoryManager`가 자동으로 새로운 기억을 LLM으로 추출합니다. 카테고리: `preference`, `context`, `pattern`, `fact`.

## 도구 라우팅

AgentOrchestrator는 LLM의 tool_call을 세 경로로 분기합니다:

| tool_call name | 라우팅 | 처리 |
|----------------|--------|------|
| `read_skill` | SkillManager | 스킬 SKILL.md 전체 내용 로드 |
| `run_skill_script` | SkillManager | 스킬 스크립트 실행 (60초 타임아웃) |
| `read_skill_reference` | SkillManager | 참조 문서 로드 |
| `workspace_*` | workspace_tools | 워크스페이스 파일/셸 도구 (아래 상세) |
| `{server}__{tool}` | MCPClientManager | MCP 서버로 도구 호출 전달 |

### 워크스페이스 LLM 도구 (7종)

활성 워크스페이스가 설정되어 있을 때만 LLM에 노출됩니다.

| 도구 | 파라미터 | 기능 |
|------|----------|------|
| `workspace_read_file` | path, offset?, limit? | 파일 읽기 (라인 번호 포함) |
| `workspace_write_file` | path, content | 파일 생성/덮어쓰기 |
| `workspace_edit_file` | path, old_string, new_string, replace_all? | 타겟 문자열 편집 |
| `workspace_list_dir` | path?, recursive?, max_depth? | 디렉토리 목록 |
| `workspace_glob` | pattern, limit? | 파일 패턴(glob) 검색 |
| `workspace_grep` | pattern, path?, glob_filter?, case_insensitive?, context?, limit? | 정규식 내용 검색 |
| `workspace_bash` | command, cwd?, timeout? | 셸 명령 실행 |

## 워크스페이스 보안 및 안전장치

### 1. 경로 순회(Path Traversal) 방지

모든 파일 작업은 `_resolve_safe_path()`를 통해 워크스페이스 루트 내부만 접근 가능합니다.

```python
# 워크스페이스 루트를 벗어나는 경로 시도 시 즉시 차단
target = (root / relative_path).resolve()
if not str(target).startswith(str(root)):
    raise ValueError("Path traversal detected")
```

- `../../etc/passwd` 등의 상대 경로 공격 차단
- 심볼릭 링크를 `.resolve()`로 실제 경로 변환 후 검증
- 파일 읽기/쓰기/편집/트리 조회 모든 경로에 적용

### 2. 위험 셸 명령 차단 (workspace_bash)

아래 10종의 정규식 패턴에 매칭되는 명령어는 실행 전에 즉시 차단됩니다:

| 차단 패턴 | 설명 | 예시 |
|-----------|------|------|
| `rm -rf /` | 루트 재귀 삭제 | `rm -rf /`, `rm -fr /` |
| Fork bomb | 무한 프로세스 생성 | `:(){ :\|:& };` |
| `dd` 디스크 덮어쓰기 | 블록 디바이스 파괴 | `dd if=/dev/zero of=/dev/sda` |
| `mkfs.*` | 파일시스템 포맷 | `mkfs.ext4 /dev/sda1` |
| 디바이스 리다이렉트 | 블록 디바이스 직접 쓰기 | `> /dev/sda` |
| curl pipe-to-shell | 원격 스크립트 실행 | `curl ... \| sh`, `curl ... \| bash` |
| wget pipe-to-shell | 원격 스크립트 실행 | `wget ... \| sh`, `wget ... \| bash` |
| `chmod -R 777 /` | 루트 전체 권한 변경 | `chmod -R 777 /` |
| `chown -R ... /` | 루트 전체 소유자 변경 | `chown -R user /` |

### 3. 셸 실행 제한

- **타임아웃**: 기본 30초, 최대 120초 (초과 시 프로세스 강제 종료)
- **출력 절삭**: stdout 최대 30,000자, stderr 최대 5,000자 (초과분 truncate)
- **작업 디렉토리 제한**: `cwd` 파라미터도 워크스페이스 루트 내부만 허용

### 4. 파일 편집 안전장치 (workspace_edit_file)

- **빈 old_string 거부**: 빈 문자열로 교체 시도 시 오류
- **유일성 검증**: `old_string`이 파일 내에 2회 이상 매치되면 `replace_all=false`일 때 거부 (매치 수 안내)

### 5. 파일 트리 필터링

보안 및 성능을 위해 파일 트리 탐색 시 아래 디렉토리/파일을 자동 제외합니다:

**제외 디렉토리:**
`.git`, `node_modules`, `__pycache__`, `.venv`, `venv`, `.next`, `dist`, `build`, `.cache`, `target`, `.idea`, `.vscode`, `.mypy_cache`, `.pytest_cache`, `.ruff_cache`, `.tox`, `egg-info`

**제외 파일:**
`.DS_Store`, `Thumbs.db`

## MCP 서버 설정 (mcp.json)

Claude Desktop 호환 스키마를 사용합니다.

```json
{
  "mcpServers": {
    "server-name": {
      "transport": "stdio",
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-everything"],
      "env": { "DEBUG": "true" },
      "enabled": true
    }
  }
}
```

지원 transport: `stdio`, `sse`, `streamable-http`

## Agent Skills 저장 경로

스킬은 `backend/skills/` 디렉토리에 저장됩니다:

```
backend/skills/
├── my-skill/
│   ├── SKILL.md              # 필수: YAML frontmatter + Markdown 지시사항
│   ├── scripts/              # 선택: 실행 스크립트 (.py, .sh, .js)
│   └── references/           # 선택: 참조 문서 (.md, .txt, .json, .yaml)
└── another-skill/
    └── SKILL.md
```

- 서버 시작 시 `skills/` 하위 폴더를 자동 스캔
- `POST /api/skills/reload`로 수동 재스캔
- UI(`/skills`)에서 등록/관리, ZIP 업로드 및 경로 임포트 지원

## Pages 트리 구조

페이지는 폴더 기반 트리로 관리됩니다. `pages.json`에 메타데이터, `pages/` 디렉토리에 HTML 파일이 저장됩니다.

- `content_type`: `html` (업로드된 파일), `url` (북마크), `folder` (폴더)
- `parent_id`: 상위 폴더 ID (`null`이면 루트)
- 폴더 삭제 시 하위 항목 재귀 삭제
- 기존 카테고리 형식에서 자동 마이그레이션 지원
