# v0 정책 고정 — File Time Search MCP
본 문서는 [PRD-FileTimeSearch-MCP.md](PRD-FileTimeSearch-MCP.md), [PRD-API.md](PRD-API.md), [PRD-ImplementationNotes.md](PRD-ImplementationNotes.md)에서 **구현이 갈릴 수 있는 지점**을 v0 기준으로 고정합니다.
목표:
- 다른 구현체(Java/TS/Python 등)라도 **동일한 외부 동작**(입력 허용/거부, 경로 규칙, 커서 재개 규칙, 제한 도달 시 동작)을 만들 수 있게 함
- 테스트 자동화에서 기준점을 고정
---
## 0) 용어
- **allowed roots**: 서버가 접근을 허용하는 루트 경로 목록
- **default root**: 요청에서 `root` 생략 시 서버가 사용하는 루트
- **root-relative path**: `root`를 기준으로 한 상대 경로
- **opaque cursor**: 클라이언트가 내부 규칙을 가정하지 않고 그대로 전달만 하는 커서
---
## 1) v0 고정 결정(요약)
v0에서는 다음을 고정합니다.
1. allowRoots 공급 방식
- ENV 기반: `ALLOW_ROOTS` + (선택) `DEFAULT_ROOT`
2. `path` 규칙
- `path`는 **반드시 상대 경로**
- 절대 경로 입력은 **tool 실행 에러(`isError=true`)로 거부**
3. 결과의 `matches[].path`
- 항상 **root 기준 상대 경로**로 반환
- 경로 구분자는 항상 `/`로 반환(Windows 포함)
4. glob 의미(문법/규칙)
- v0는 `**/*.md` 같은 패턴을 **지원하는 의미론**으로 고정(아래 6장)
5. 스캔 제한 도달 시
- v0 기본은 **부분 결과 반환 + `nextCursor` 제공**
- 단, 재개 커서를 안전하게 만들 수 없으면 `isError=true`로 실패
6. 날짜 입력
- v0는 [PRD-API.md](PRD-API.md)대로 **ISO 8601 date-time 문자열만 허용**
7. 정렬 안정성(tie-breaker)
- `time_*`: `(time, path)`
- `path_asc`: `(path, time)`
---
## 2) allowRoots / defaultRoot 정책
### 2.1 설정 소스(필수)
서버는 시작 시 다음 환경변수를 읽는다.
- `ALLOW_ROOTS` (필수)
- 하나 이상의 루트 경로 목록
- 구분자: `;` 또는 `,` (둘 다 지원)
- 각 항목은 trim 후 사용
- `DEFAULT_ROOT` (선택)
- 요청에서 `root`가 생략되었을 때 사용할 루트
### 2.2 규칙
- 서버는 `ALLOW_ROOTS`가 비어 있으면 **실행에 실패**(프로세스 시작 자체 실패)해야 한다.
- `DEFAULT_ROOT`가 지정되었다면:
- `DEFAULT_ROOT`는 `ALLOW_ROOTS` 중 하나여야 한다(정규화 후 비교).
- 아니라면 **실행에 실패**해야 한다.
- 요청에서 `root`가 생략되면:
- `DEFAULT_ROOT`가 있으면 그것을 사용
- 없으면 `ALLOW_ROOTS`의 첫 번째 항목을 사용
### 2.3 경로 정규화와 비교
- 모든 allow root는 서버 시작 시 **절대 경로로 정규화**한다.
- Windows에서는 드라이브 문자/대소문자 차이가 있으므로, 비교는 다음을 권장한다.
- 정규화(absolute + normalize) 후
- Windows에서는 대소문자 무시 비교(파일시스템 규칙에 맞춤)
---
## 3) `path` 입력 규칙(정규화/검증)
### 3.1 허용/금지
- `path`는 **root 하위 상대 경로**만 허용한다.
- 다음은 금지이며, 들어오면 **tool 실행 에러(`isError=true`)**:
- 절대 경로(예: `C:\Users\...`, `\\server\share\...`, `/etc/...`)
- 드라이브/UNC/루트 접두가 있는 입력
- 정규화 후 상위로 탈출하는 입력(예: `..`, `a/../..`)
### 3.2 빈 값 처리
- `path` 생략 또는 빈 문자열(`""`) 또는 `"."` 는 “root 자체”를 의미한다.
### 3.3 구분자 처리
- 입력의 `\`(Windows 역슬래시)는 내부 처리에서 `/`로 치환하여 해석할 수 있다.
- 단, 출력은 항상 `/` 구분자를 사용한다(4장 참고).
---
## 4) 결과 `matches[].path` 반환 규칙
### 4.1 경로 형식
- `matches[].path`는 항상 **root 기준 상대 경로**로 반환한다.
- **절대 경로는 반환하지 않는다.** (루트 경로 노출 최소화)
### 4.2 구분자
- `matches[].path`는 항상 `/`를 구분자로 사용한다.
- Windows에서도 동일
### 4.3 정규화
- `matches[].path`는 `.` 세그먼트를 포함하지 않도록 정규화한다.
- `..` 세그먼트는 절대 포함되면 안 된다.
---
## 5) `from/to` 입력(시간) 정책
### 5.1 포맷
- v0에서는 `from`, `to`를 **ISO 8601 date-time 문자열**로만 허용한다.
- 예: `2025-12-15T00:00:00Z`, `2025-12-15T09:00:00+09:00`
### 5.2 경계 규칙
- `from`은 inclusive
- `to`는 exclusive
### 5.3 범위 유효성
- `from`과 `to`가 모두 있을 때:
- `from > to` 는 허용되지 않는다 → tool 실행 에러
- `from == to` 는 **유효한 입력**이며 결과는 항상 0개여야 한다(`matches=[]`, `nextCursor=null`, `isError=false`)
---
## 6) 탐색 깊이(`recursive`/`maxDepth`) 정책
### 6.1 `maxDepth`의 의미(재귀 탐색 시)
- `recursive=true`일 때, `maxDepth`는 **시작 경로(`root + path`)를 깊이 0**으로 하는 최대 깊이 제한이다.
- `maxDepth=0`이면 **시작 경로 자체만** 대상으로 한다.
- 시작 경로가 디렉터리인 경우: 하위 항목은 탐색/반환하지 않는다. `includeDirectories=true`일 때만 그 디렉터리 1개가 결과에 포함될 수 있다.
- 시작 경로가 파일인 경우: `includeFiles=true`일 때만 그 파일 1개가 결과에 포함될 수 있다.
### 6.2 `recursive=false`일 때
- `recursive=false`이면 `maxDepth`는 무시한다([PRD-API.md](PRD-API.md) 설명과 동일).
- 이 모드에서 시작 경로가 디렉터리이면 **직접 자식(1 레벨)** 만 대상으로 한다.
## 7) glob 의미론(v0)
### 6.1 적용 대상
- glob은 **root + path 하위**에서 발견된 항목들의 **root-relative path**에 적용한다.
- glob 매칭은 `matches[].path`와 같은 형식(상대 + `/`)으로 수행한다.
### 6.2 문법(의미)
v0는 아래 의미를 만족하는 엔진을 사용해야 한다(라이브러리는 구현체 선택).
- `/`는 경로 구분자
- `*`는 한 세그먼트 내에서 0개 이상 문자 매칭(`/` 제외)
- `?`는 한 세그먼트 내에서 1개 문자 매칭(`/` 제외)
- `**`는 0개 이상 세그먼트 매칭(디렉터리 경계를 포함)
- 패턴은 기본적으로 전체 경로에 대해 매칭된다.
- 예: `**/*.md` 는 `a.md`, `dir/a.md`, `dir/sub/a.md` 등을 포함
### 6.3 기본값
- `glob` 생략 시 “모든 항목 매칭”으로 간주한다.
## 8) 스캔 제한/부분 결과/페이지네이션 정책
### 7.1 기본 원칙
- 서버는 안전을 위해 스캔/시간 제한을 둔다(값은 별도 문서에서 고정: [PRD-Defaults-v0.md](PRD-Defaults-v0.md)).
- 제한 도달 시 v0 기본 동작은 다음과 같다.
### 7.2 제한 도달 시 동작(v0)
- 제한(`timeoutMs`, `maxFilesScanned`, `maxDirectoriesScanned`)에 도달하면:
- 가능한 경우: **부분 결과를 반환**하고 `nextCursor`를 설정한다.
- 불가능한 경우(예: 커서 생성에 필요한 마지막 정렬 키를 만들 수 없음): **tool 실행 에러(`isError=true`)**로 실패한다.
주의:
- 부분 결과 반환은 “결과가 완전함을 보장”하지 않는다.
- `stats`로 스캔 규모를 함께 반환해 클라이언트가 이어서 호출할지 판단할 수 있게 한다.
## 9) cursor 내부 규칙(opaque지만 v0에서 고정)
### 8.1 외부 계약
- `cursor`는 opaque이며 클라이언트는 내용을 해석하지 않는다.
- 서버는 잘못된 커서를 받으면 tool 실행 에러로 안내한다.
### 8.2 v0 권장 인코딩(규범)
v0에서는 커서를 다음 방식으로 생성/해석하는 것을 표준으로 권장한다.
- 커서 payload: JSON object
- 인코딩: UTF-8 → base64url(패딩 제거)
권장 payload 필드:
- `v`: number (버전, v0에서는 1)
- `s`: string (`sort` 값)
- `t`: number | null (정렬에 사용한 time key의 epoch millis, UTC)
- `p`: string (root-relative path, `/` 구분자)
예(개념):
```json
{"v":1,"s":"time_desc","t":1765756800000,"p":"docs/README.md"}
```
### 8.3 커서가 의미하는 것
- 커서는 “이전 페이지의 마지막 항목 이후”부터 재개를 의미한다.
- 재개 조건은 정렬별로 아래를 따른다.
- `time_desc` / `time_asc`: `(time, path)` 기준으로 strictly after
- `path_asc`: `(path, time)` 기준으로 strictly after
## 10) 정렬 규칙(안정 정렬)
정렬은 결과의 중복/누락을 방지하기 위해 **항상 tie-breaker**를 포함한다.
- `sort=time_desc`
- primary: timeField 내림차순
- tie-breaker: path 오름차순
- `sort=time_asc`
- primary: timeField 오름차순
- tie-breaker: path 오름차순
- `sort=path_asc`
- primary: path 오름차순
- tie-breaker: timeField 오름차순
## 11) 심볼릭 링크/리파스 포인트 정책
- v0 기본값: **follow 하지 않음**
- Windows: reparse point/junction 포함
- 구현이 follow를 선택적으로 지원하더라도, 다음 규칙은 MUST:
- 최종 대상(real path)이 allowed root 밖이면 거부
## 12) tool 에러 vs 프로토콜 에러(재확인)
- Unknown tool / malformed request 등은 프로토콜 에러(JSON-RPC error)
- 입력값 오류/권한/정책 위반/스캔 제한 등은 tool 실행 에러(`isError=true`)
이 규칙은 MCP Tools 스펙의 권장 사항과 일치한다.
## 13) 관련 문서
- 스키마/계약: [PRD-API.md](PRD-API.md)
- 기본값/제한 테이블(v0): [PRD-Defaults-v0.md](PRD-Defaults-v0.md)
- 에러 코드 표준(v0): [PRD-ErrorCodes-v0.md](PRD-ErrorCodes-v0.md)
- 예시 모음(v0): [PRD-Examples-v0.md](PRD-Examples-v0.md)