semantic-search-mcp
semantic-search-mcp
작고 자체 완결적인 RAG-lite 검색 엔진입니다. 디스크의 파일을 인덱싱하고 "이 질의와 의미적으로 관련된 것은 무엇인가"에 대한 답을 반환합니다. 그 이상은 하지 않습니다. LLM을 호출하지 않으며 답변을 생성하지도 않습니다. 가장 관련성 높은 텍스트 청크(파일, 줄, 점수)를 반환하여, 이를 소비하는 주체(사람, 스크립트, 또는 MCP를 통한 LLM)가 어떻게 처리할지 결정할 수 있도록 합니다.
모든 것은 첫 실행 후 로컬 및 오프라인에서 실행됩니다:
임베딩:
@huggingface/transformers가Xenova/all-MiniLM-L6-v2를 int8 양자화 가중치로 CPU에서 실행합니다. GPU, API 키, 질의 시 네트워크 호출이 필요 없습니다.벡터 저장소:
@lancedb/lancedb— 임베디드, 파일 기반 벡터 데이터베이스입니다. 서버 프로세스나 Docker가 필요 없습니다.인터페이스: CLI와 stdio MCP 서버를 제공하므로, MCP를 인식하는 모든 에이전트(Claude Code, Cursor, Zed 등)가 코퍼스를 직접 검색할 수 있습니다.
빠른 시작
npm install -g @adborroto/semantic-search-mcp
semantic-search add ~/code/my-project # add a folder to the corpus
semantic-search index # embed it (incremental on later runs)
semantic-search search "how does the retry logic work"이것이 전체 설정입니다. 수동으로 작성해야 하는 설정 파일은 없습니다. add가 생성하고 관리합니다. 아무것도 설치하지 않고 시도하려면:
npx @adborroto/semantic-search-mcp add ~/code/my-project설치 크기 주의: 약 950MB의 의존성과 첫 사용 시 다운로드되는 약 25MB의 임베딩 모델이 필요합니다. 거의 대부분이 이 계층에서 피할 수 없는 네이티브 바이너리입니다.
@lancedb/lancedb(약 430MB, 플랫폼 바이너리 포함)와 ONNX 런타임(약 300MB, 모든 플랫폼용 빌드를 하나의 패키지로 제공)입니다. 둘 다 한 번 캐시되며, 첫 실행 이후에는 모든 것이 오프라인에서 작동합니다.
Related MCP server: rag-retriever-mcp
요구 사항
Node.js >= 22 (폴백 백엔드에서 사용하는
node:sqlite는 22부터 안정화됨).디스크 약 950MB(의존성) 및 약 25MB(임베딩 모델), 인덱싱된 청크당 약 1–3KB 추가.
GPU, 외부 서비스, 데이터베이스 서버 불필요.
"RAG-lite"인 이유
전체 RAG 파이프라인은: 청크 검색 → LLM에 전달 → LLM이 답변 작성입니다. 이 프로젝트는 첫 번째 단계에서 멈춥니다. 이는 단순하고, 빠르며, 실행 비용이 저렴하고, 추론하기 쉬우며, 자체적인 생성 계층을 번들링하는 대신 이미 사용 중인 LLM이나 에이전트 프레임워크와 깔끔하게 결합됩니다.
코퍼스 관리
semantic-search add ~/code/api ~/notes # add one or more folders
semantic-search list # show what's configured
semantic-search remove api # by folder name...
semantic-search remove ~/notes # ...or by path
semantic-search config # where config + index actually liveadd는 각 경로가 실제 디렉토리인지 확인하고, 절대 경로로 변환하며, 중복(심볼릭 링크를 통해 도달한 동일 디렉토리 포함)을 건너뜁니다. remove는 해당 폴더의 청크를 인덱스에서 제거하여 결과에 나타나지 않도록 합니다. 코퍼스에서 제거하되 검색 가능하게 유지하려면 --keep-index를 전달하세요.
저장 위치
설정과 인덱스는 XDG 기본 디렉토리 사양을 따르므로 업그레이드 후에도 유지되며 모든 설치 방법에서 공유됩니다:
항목 | 위치 |
설정 |
|
인덱스 + 모델 캐시 |
|
SS_CONFIG_PATH, SS_INDEX_DIR, SS_MODEL_CACHE_DIR 또는 표준 XDG_CONFIG_HOME / XDG_DATA_HOME으로 재정의할 수 있습니다. SS_STORE_BACKEND=sqlite는 폴백 백엔드를 강제합니다.
인덱스에는 인덱싱한 모든 내용의 원문이 포함됩니다. 비공개 코드를 가리키면
~/.local/share/semantic-search/에 해당 내용이 일반 텍스트로 저장됩니다. 절대 커밋하지 말고 버그 리포트에 첨부하지 마세요.
모든 옵션은 src/config.js에 문서화되어 있습니다 — 청크 크기, 무시 패턴, 모델 이름, top-k, 동시성 등. config.json을 직접 편집해도 작동합니다. add/remove는 자신이 소유하지 않은 키는 보존합니다.
사용법
인덱싱
semantic-search index # all configured folders
semantic-search index ~/code/one-project # just this folder, ignoring config
semantic-search index --force # reprocess everything인덱싱은 증분식입니다: 변경되지 않은 파일은 수정 시간으로 건너뛰고, 내용이 실제로 변경되지 않은(터치만 된) 파일은 재임베딩을 건너뛰며, 디스크에서 삭제된 파일은 인덱스에서 제거됩니다. 실제로 변경된 것만 다시 처리됩니다.
여러 폴더가 설정된 경우, index는 순차적으로 탐색하며 폴더별 헤더와 결합된 총계를 표시합니다:
[1/3] my-api /home/me/code/my-api ─────────────────────────────
↺ indexed src/auth/middleware.js (8 chunks)
2 indexed 1,203 skipped 16 chunks 4.1s
[2/3] my-app /home/me/code/my-app ─────────────────────────────
...
──────────────────────────────────────────────────────────────
total 5 indexed 3,891 skipped 0 deleted 41 chunks 12.3s각 index <path> 호출은 해당 경로 아래의 파일에 대해서만 오래된 항목을 정리하므로, 폴더 B를 인덱싱해도 폴더 A의 항목은 건드리지 않습니다.
유용한 플래그: --max-files <n>은 N개의 새 파일 후 중단(대규모 코퍼스에서 메모리 제한), --concurrency <n>은 병렬 처리 설정, --verbose는 각 파일을 stderr에 기록합니다.
검색
semantic-search search "how does the retry logic work" -k 5파일 경로, 줄 번호, 점수, 텍스트 미리보기의 테이블을 출력합니다.
검색은 하이브리드입니다: 질의는 두 개의 독립적인 갈래로 전달됩니다 — 임베딩에 대한 벡터 검색과 동일한 청크에 대한 BM25 전문 검색 — 두 순위는 Reciprocal Rank Fusion으로 융합됩니다. 갈래는 서로 다르게 실패합니다: 벡터 갈래는 의미적 핸들이 없는 정확한 식별자, 오류 문자열, 설정 키를 놓칩니다; 어휘 갈래는 의역을 놓칩니다. 둘 다 실행하는 것은 재현율을 높이기 위한 것이며, 점수 대신 순위로 융합하면 제한 없는 BM25 점수가 코사인 유사도를 압도하는 것을 방지합니다.
config.json에서 "hybridSearch": false로 설정하면 벡터 전용 검색이 되고, "rrfK"로 RRF의 순위 평활화 상수(기본값 60, 논문 기준)를 조정할 수 있습니다.
인덱싱되는 대상
폴더를 지정하면 그 안의 모든 것이 재귀적으로 인덱싱됩니다. "지원되는" 파일 확장자 목록은 없습니다 — .dart, .kt, .java, .tsx, .sql, .erb 등 텍스트 파일은 모두 그대로 인덱싱되며, .pdf와 .docx는 먼저 파서를 거칩니다.
네 가지가 제외됩니다:
git이 무시하는 것 (폴더가 git 저장소인 경우).
.gitignore는 모든 깊이에서 존중되며,.git/info/exclude, 전역 제외 파일, 부정 패턴(!keep.this)도 포함됩니다. 이는git ls-files에 위임되어 다시 구현되지 않으므로 git과 정확히 일치합니다. 즉, 프로젝트가 이미 무시하는 생성된 출력물과 벤더 출력물은 두 번째 목록을 유지하지 않고도 인덱스에서 제외됩니다..indexignore규칙 (아래 참조) — 커밋되었지만 검색 가능하지 않아야 하는 콘텐츠(픽스처, 스냅샷, 체크인된 비밀 템플릿).바이너리 파일 — 확장자(이미지, 아카이브, 폰트, 컴파일된 객체, 모델 가중치) 및 내용 기준 — 처음 4KB에 NUL 바이트가 있으면 바이너리로 간주하며, 이는
grep -I와 동일한 휴리스틱입니다. 이는 토크나이저에 비텍스트 바이트가 들어가지 않도록 하는 안전 조치이며, 무엇이 인덱싱할 가치가 있는지에 대한 판단이 아닙니다.500,000바이트 초과 파일(
maxFileSizeBytes) — 생성된 단일 줄 메가바이트 파일이 메모리를 소진하는 것을 방지하는 주요 장치입니다.
심볼릭 링크는 건너뛰므로(따르지 않음) 폴더 내에 심볼릭 링크가 있어도 외부 콘텐츠가 인덱스에 포함되지 않습니다.
git 저장소가 아닌 폴더의 경우 의존할 .gitignore가 없으므로 작은 내장 목록(node_modules/, .git/, dist/, build/, coverage/, vendor/ 등)이 여전히 적용됩니다.
더 많은 항목을 제외하려면 gitignore 스타일의 .indexignore를 다음 위치 중 하나에 넣으세요:
인덱싱하는 폴더 내부 — 패턴은 해당 폴더 기준;
설정 파일 옆(
~/.config/semantic-search/.indexignore) — 모든 곳에 적용됩니다.
iOS, Android, Flutter, Ruby, JVM 빌드 아티팩트를 다루는 시작점은 .indexignore.example을 참조하세요.
MCP 서버
semantic-search mcp여섯 가지 도구를 노출하는 stdio MCP 서버를 시작합니다.
search(query, k?) — 의미 검색, 원시 JSON 반환:
[{ filePath, text, score, offset, startLine }, ...]gather(query, k?, contextLines?) — 동일한 검색, 컨텍스트 창에 바로 넣을 수 있는 단일 포맷된 마크다운 블록으로 반환:
### [1/5] my-api · src/auth/session.js · line 42 · score 0.923
```
...chunk text...
```contextLines(기본값 0)는 각 청크 주변의 N개 추가 줄을 소스 파일에서 읽어옵니다. 청크 경계가 필요한 컨텍스트를 잘라낼 때 유용합니다.
list_folders() — 설정된 모든 폴더와 그 이름 및 절대 경로. 에이전트가 어떤 코퍼스가 있는지 알 수 있는 좋은 첫 호출입니다.
cat_file(filePath, startLine?, endLine?) — search/gather에서 반환된 절대 경로로 파일 읽기. 설정된 폴더로 제한됩니다(보안 참조).
grep(pattern, folder?, fileGlob?, caseSensitive?, maxResults?) — 리터럴 또는 정규식 검색으로 코퍼스 전체에서 정확한 일치가 필요할 때 사용. 인덱서가 인덱싱할 파일 목록과 정확히 동일하게 필터링되므로, gitignore 및 .indexignore된 파일이 정확 일치 검색을 통해 유출될 수 없습니다.
my-api · src/auth/session.js:42 export function createSession(user) {index(root?, force?, maxFiles?, concurrency?) — 증분 재인덱싱을 트리거하여 에이전트가 셸을 사용하지 않고 코퍼스를 새로고침할 수 있습니다.
모든 검색 도구는 CLI와 동일한 순위 및 파일 확인 코드를 공유합니다. 어느 것도 다시 구현하지 않습니다.
MCP 클라이언트에 등록
Claude Code:
claude mcp add --scope user semantic-search -- semantic-search mcp
claude mcp list # should show "✔ Connected"JSON 서버 정의를 받는 모든 클라이언트:
{
"mcpServers": {
"semantic-search": {
"command": "semantic-search",
"args": ["mcp"]
}
}
}여기서는 npx보다 전역 설치를 선호하세요: 기본 npx는 서버가 시작될 때마다 패키지를 다시 확인하여 시작 지연 시간을 추가하고 업그레이드를 예고 없이 가져옵니다. npx를 사용한다면 버전을 고정하세요 — npx -y @adborroto/semantic-search-mcp@0.1.0 mcp.
새 MCP 서버는 일반적으로 세션이 시작될 때만 인식되므로, 등록 후 새 세션을 시작하세요.
보안
이것은 로컬, 단일 사용자 도구로 간단한 신뢰 모델을 가집니다: 설정된 폴더 내의 모든 것은 서버에 도달할 수 있는 모든 MCP 클라이언트가 읽을 수 있습니다.
cat_file은 설정된 폴더 외부의 경로를 거부하며, 먼저 심볼릭 링크를 확인하여 폴더 내에 심볼릭 링크가 있어도 이를 이용해 탈출할 수 없도록 합니다.grep은 인덱서가 구축하는 파일 목록과 동일하게 필터링됩니다 — git의 무시 규칙과.indexignore— 따라서 인덱싱에서 의도적으로 제외된 파일이 정확 일치 검색을 통해 유출되지 않습니다.하위 프로세스는 argv 배열로 생성되며(셸 사용 안 함), 패턴이 명령을 주입할 수 없습니다.
이러한 점을 고려하여, LLM 제공자에게 넘기고 싶지 않은 코퍼스를 가리키지 마세요 — 청크는 요청한 클라이언트에게 반환됩니다. SECURITY.md를 참조하세요.
작동 방식
파일 발견
규칙은 "폴더 아래 모든 것을 인덱싱"이며, 유일한 흥미로운 부분은 무엇을 인덱싱하지 않을지입니다. git의 무시 의미론(중첩된 .gitignore 파일, 부정, info/exclude, 전역 제외 파일)을 다시 구현하는 대신, git 루트는 다음과 같이 열거됩니다:
git ls-files -z --cached --others --exclude-standard추적된 파일과 무시되지 않은 추적되지 않은 파일, 실행되는 디렉토리로 범위가 지정됩니다. git이 무시하는 것은 구조적으로 존재하지 않습니다. git이 아닌 폴더는 내장 패턴 목록으로 일반 재귀 탐색을 사용합니다.
동일한 함수가 인덱서와 MCP grep 도구(src/ignoreRules.js)를 모두 지원합니다. 이는 의도적입니다: grep은 실제 grep -r을 호출하며, 이는 gitignore된 빌드 출력 내에서도 기꺼이 일치를 보고하므로, 결과를 인덱서 자체 파일 목록으로 필터링합니다. 둘이 별도로 규칙을 도출하면 차이가 발생하고 무시 목록이 경계 역할을 하지 못하게 됩니다.
하이브리드 검색
질의는 두 갈래로 병렬 실행됩니다:
벡터 — 질의를 임베딩하고, 코사인 거리로 최근접 이웃을 가져온 다음, 리터럴 질의 용어를 포함하는 청크에 대해 작은 어휘 부스트로 목록을 재정렬합니다.
어휘 — 동일한 청크 텍스트에 대한 BM25, LanceDB 전문 인덱스를 통해(
sqlite폴백은node:sqlite가 FTS5를 보장하지 않으므로 JS에서 BM25를 계산합니다).
두 순위는 RRF로 융합됩니다. 각 목록이 반환하는 모든 청크에 1 / (60 + rank)를 기여하고, 기여도를 합산합니다. 점수가 아닌 순위를 기준으로 융합하는 것이 핵심입니다. 코사인 유사도는 [-1, 1] 범위인 반면 BM25는 상한이 없으므로, 원시 점수를 더하거나 평균내면 코퍼스 크기에 따라 한쪽이 조용히 다른 쪽을 압도할 수 있습니다.
두 개의 검색 방식을 사용하는 이유: 벡터 검색 결과에 어휘적 부스트를 적용하면 벡터 쿼리가 이미 반환한 결과의 순서만 바꿀 수 있습니다. 정확한 용어 일치(오류 코드, 심볼 이름, 의미적 이웃이 없는 설정 키)만으로 신호가 발생하는 청크는 벡터 풀 밖에 있으면 도달할 수 없었습니다. 어휘적 검색은 이를 독립적으로 검색합니다. 이는 재순위화가 아니라 재현율(recall) 수정이며, 검색 점수가 0.9가 아닌 0.03처럼 보이는 이유입니다. 이는 RRF 합계이지 코사인 유사도가 아닙니다. 순서만 의미가 있습니다.
전문(full-text) 인덱스는 각 인덱싱 실행이 끝날 때 재구축됩니다. FTS 인덱스는 구축 이후 추가된 행을 포함하지 않기 때문입니다. 그렇지 않으면 실행에서 방금 작성한 청크가 어휘적 검색에 보이지 않게 됩니다.
청킹(Chunking)
텍스트는 문단으로 분할된 후, 임베딩 모델의 실제 토크나이저(문자 수 근사치가 아님)로 계산된 약 200 토큰 크기로 약 35 토큰의 중첩을 두고 탐욕적으로 청크로 묶입니다. 이는 임의적이지 않습니다. all-MiniLM-L6-v2는 256 토큰 윈도우를 가지며 더 긴 내용은 자동으로 잘라내므로, 청크는 [CLS]/[SEP] 토큰을 위한 여유를 두고 그 안에 들어가도록 크기가 조정됩니다. 중첩은 추가로 제한되어, 중첩과 다음 문단을 합쳐도 그 한계를 절대 넘지 않도록 합니다. 그렇지 않으면 청크의 끝부분이 임베딩 시점에 잘리면서도 search에서는 반환될 수 있습니다.
하드 한계보다 큰 단일 문단(축소된 번들, 하나의 거대한 로그 줄)은 동일한 중첩 로직으로 단어 수준 패킹으로 대체되며, 500자를 초과하는 단일 "단어"는 먼저 분할되어 토크나이저에 한 번에 큰 덩어리가 전달되지 않습니다.
토큰 수는 문단/단어당 한 번 계산되어 중첩 계산 중 재사용을 위해 캐시됩니다. 이전 버전은 중첩 조회 시마다 다시 토큰화했는데, 작은 입력에서는 괜찮았지만 대규모 저장소에서는 CPU 폭주와 수 GB 메모리 증가를 유발했습니다. 청커를 확장한다면 이 속성을 유지하십시오.
증분 재인덱싱(Incremental reindexing)
별도의 매니페스트는 없습니다. 벡터 저장소 자체가 매니페스트입니다. 저장된 모든 청크는 소스 파일의 mtimeMs와 sha256 콘텐츠 해시를 함께 저장합니다. 각 실행 시:
파일의 디스크 상
mtime이 저장된 값과 일치하면 파일을 읽지 않고 건너뜁니다.mtime이 변경되었지만 콘텐츠 해시가 동일하면(touch의 경우) 재임베딩을 건너뜁니다.그렇지 않으면 해당 파일의 이전 청크를 삭제하고 새로 임베딩된 청크를 삽입합니다.
목록화 후, 더 이상 디스크에 없고(인덱싱 중인 루트 아래에 있는) 인덱싱된 경로는 제거됩니다.
스토리지 백엔드(Storage backends)
기본값은 LanceDB입니다: 임베디드, 파일 기반, 실제 벡터 검색. node:sqlite + 무차별 코사인 대체(src/store/sqliteFallbackStore.js)는 LanceDB의 네이티브 바인딩이 로드되지 않는 환경(샌드박스 컨테이너, 특이한 아키텍처)을 위해 동일한 인터페이스(src/store/vectorStore.js)를 구현합니다. SS_STORE_BACKEND=sqlite로 전환합니다.
대체 백엔드는 검색 시 전체 테이블 스캔을 수행합니다. 수만 개의 청크에는 괜찮지만 그 이상은 어렵습니다. LanceDB의 기본 메트릭은 L2이지 코사인이 아니므로, 이 프로젝트는 모든 쿼리에 명시적으로 .distanceType('cosine')을 설정합니다. 임베딩은 정규화된 벡터로 비교되기 때문입니다.
프로젝트 구조
src/
config.js Defaults + config file resolution (XDG) — the only source of tunables
configFile.js Read/modify/write the config file (backs add/remove/list)
embeddings.js transformers.js pipeline + tokenizer (lazy singletons)
chunker.js Token-aware paragraph packing with overlap
ignoreRules.js What is indexable: git ignore rules + .indexignore + binary filter,
shared by the indexer and grep so they can't drift apart
safePath.js Path confinement for the MCP file-reading tools
version.js Version read from package.json
extractors/ text (anything not binary), pdf (pdf-parse), docx (mammoth)
store/
vectorStore.js Storage interface + backend selector
lancedbStore.js LanceDB implementation (default)
sqliteFallbackStore.js node:sqlite + manual cosine fallback
indexer.js List + extract + chunk + embed + incremental upsert/prune
search.js Hybrid retrieval: vector + BM25 arms fused with RRF — shared by CLI and MCP
mcp-server.js MCP stdio server: the six tools above
index.js CLI entrypoint (commander)
scripts/index-all.sh Batched indexing for very large corpora on constrained hosts (Linux)개발
git clone https://github.com/adborroto/semantic-search-mcp.git
cd semantic-search-mcp
npm install
npm test # unit + end-to-end (node:test, no framework)
npm run test:unit # skip the slow end-to-end test
npm run lint체크아웃 루트의 config.json은 XDG 위치보다 우선하므로, 실제 설정을 건드리지 않고 스크래치 코퍼스로 개발할 수 있습니다. 테스트는 항상 임시 디렉토리에 기록합니다. CONTRIBUTING.md를 참조하십시오.
범위 외 (설계상)
답변 생성. 청크를 반환하며, 답변을 반환하지 않습니다. 직접 LLM에 전달하십시오.
두 번째 모델로 재순위화. 하이브리드 검색과 RRF는 의존성이 없으며 대부분의 효과를 얻을 수 있습니다. 하지만 크로스-인코더 재순위화기는 아닙니다.
웹 UI. CLI와 MCP만 제공합니다.
대규모 코퍼스. 개인 또는 팀 규모의 문서 및 코드 코퍼스(수만 개의 청크, 수백만 개는 아님)를 위해 구축되었습니다. 두 백엔드 모두 그 규모를 가정합니다.
라이선스
This server cannot be deployed
Maintenance
Related MCP Connectors
Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.
Agentic search over your Dewey document collections from any MCP-compatible client.
Hosted MCP memory: save sessions/decisions once, search from Claude, Cursor, ChatGPT. EU-hosted FTS.
Related MCP Servers
- AlicenseAqualityDmaintenanceLocal-first RAG indexing and semantic search MCP server. Enables document retrieval and context-aware queries using local embedding models.313 npmMIT
- FlicenseAqualityDmaintenanceA local-first document retrieval engine that mounts as an MCP tool for agents to index files, search for relevant passages, and let the agent's own LLM answer.4-

devitway-rag-starterofficial
FlicenseNot gradedqualityDmaintenanceMinimal local RAG stack with an MCP server that provides document search for any agent.1-- AlicenseAqualityBmaintenanceIndexes local Markdown/text files into a SQLite database with vector embeddings and provides MCP tools for semantic search without cloud dependencies.3AGPL 3.0