hwp-rag-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .coverage
5
+ .mypy_cache/
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ .venv/
9
+ build/
10
+ dist/
11
+ htmlcov/
12
+
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.1.0] - Unreleased
6
+
7
+ ### Added
8
+
9
+ - Local HWP/HWPX parsing, element-aware chunking, multilingual E5 embeddings, and FAISS search.
10
+ - Safe FAISS and JSON persistence without pickle deserialization.
11
+ - Explicit index status/sync CLI and MCP tools for Codex and Claude Code.
12
+ - Korean-first setup and operations guide.
13
+
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HWP RAG MCP Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,191 @@
1
+ Metadata-Version: 2.4
2
+ Name: hwp-rag-mcp
3
+ Version: 0.1.0
4
+ Summary: Local HWP/HWPX retrieval for Codex and Claude Code over MCP
5
+ Project-URL: Homepage, https://github.com/jaypakdevkr/HWP-RAG-MCP
6
+ Project-URL: Repository, https://github.com/jaypakdevkr/HWP-RAG-MCP
7
+ Project-URL: Issues, https://github.com/jaypakdevkr/HWP-RAG-MCP/issues
8
+ Author: HWP RAG MCP Contributors
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: faiss,hwp,hwpx,korean,langchain,mcp,rag
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Text Processing :: Indexing
22
+ Requires-Python: <3.14,>=3.10
23
+ Requires-Dist: faiss-cpu<2,>=1.12
24
+ Requires-Dist: langchain-community<1,>=0.4
25
+ Requires-Dist: langchain-huggingface<2,>=1
26
+ Requires-Dist: langchain-hwp-hwpx-loader<0.2,>=0.1.1
27
+ Requires-Dist: langchain-text-splitters<2,>=1
28
+ Requires-Dist: mcp<2,>=1.20
29
+ Requires-Dist: platformdirs<5,>=4
30
+ Requires-Dist: sentence-transformers<6,>=5
31
+ Provides-Extra: dev
32
+ Requires-Dist: build>=1.2; extra == 'dev'
33
+ Requires-Dist: mypy>=1.11; extra == 'dev'
34
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
35
+ Requires-Dist: pytest>=8; extra == 'dev'
36
+ Requires-Dist: ruff>=0.7; extra == 'dev'
37
+ Requires-Dist: twine>=5; extra == 'dev'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # HWP RAG MCP
41
+
42
+ `hwp-rag-mcp`는 바탕화면의 `.hwp`/`.hwpx` 문서를 로컬에서 파싱하고 검색할 수 있게
43
+ 해 주는 MCP 서버입니다. 문서는 로컬 E5 임베딩과 FAISS로 색인되며, Codex 또는 Claude
44
+ Code가 검색 근거를 받아 최종 답변을 작성합니다.
45
+
46
+ - 외부 문서 API나 OpenAI API 키가 필요하지 않습니다.
47
+ - 문서 내용과 벡터 인덱스는 사용자 컴퓨터에만 저장됩니다.
48
+ - 표, 각주, 미주, 메모의 LangChain 메타데이터를 유지합니다.
49
+ - 파일 변경 후 사용자가 명시적으로 동기화해야 하므로 검색 동작이 예측 가능합니다.
50
+
51
+ > 현재 버전은 개인용·소규모 데모 문서함을 대상으로 합니다. OCR, 이미지 검색, 자동 파일
52
+ > 감시, 원격 MCP 전송, 서버 내부 답변 생성은 지원하지 않습니다.
53
+
54
+ ## 1. 준비
55
+
56
+ Python 3.10~3.13과 [uv](https://docs.astral.sh/uv/) 사용을 권장합니다.
57
+
58
+ ```bash
59
+ mkdir -p ~/Desktop/dataset
60
+ ```
61
+
62
+ 검색할 HWP/HWPX 파일을 `~/Desktop/dataset`에 넣습니다. 하위 폴더도 재귀적으로
63
+ 검색합니다. 심볼릭 링크와 다른 확장자는 무시합니다.
64
+
65
+ ## 2. 최초 인덱스 생성
66
+
67
+ ```bash
68
+ uvx --from hwp-rag-mcp hwp-rag-mcp sync \
69
+ --dataset-dir ~/Desktop/dataset
70
+ ```
71
+
72
+ 최초 실행에서는 `intfloat/multilingual-e5-small` 모델과 로컬 추론 의존성을 다운로드하므로
73
+ 시간과 디스크 공간이 필요합니다. 이후 모델 캐시와 인덱스가 유지되는 동안에는 오프라인
74
+ 검색이 가능합니다.
75
+
76
+ 상태 확인과 강제 재구축:
77
+
78
+ ```bash
79
+ uvx --from hwp-rag-mcp hwp-rag-mcp status \
80
+ --dataset-dir ~/Desktop/dataset
81
+
82
+ uvx --from hwp-rag-mcp hwp-rag-mcp sync \
83
+ --dataset-dir ~/Desktop/dataset --force
84
+ ```
85
+
86
+ `--dataset-dir`를 생략하면 `HWP_RAG_DATASET_DIR` 환경변수, 그다음
87
+ `~/Desktop/dataset`을 사용합니다.
88
+
89
+ ## 3. Codex 연결
90
+
91
+ CLI에서 로컬 STDIO MCP 서버를 등록합니다.
92
+
93
+ ```bash
94
+ codex mcp add hwp-rag -- \
95
+ uvx --from hwp-rag-mcp hwp-rag-mcp serve \
96
+ --dataset-dir ~/Desktop/dataset
97
+ ```
98
+
99
+ 직접 설정할 경우 `~/.codex/config.toml`에 다음을 추가할 수 있습니다. MCP 프로세스가 셸을
100
+ 거치지 않는 환경을 고려하면 절대경로가 가장 안전합니다.
101
+
102
+ ```toml
103
+ [mcp_servers.hwp-rag]
104
+ command = "uvx"
105
+ args = [
106
+ "--from", "hwp-rag-mcp",
107
+ "hwp-rag-mcp", "serve",
108
+ "--dataset-dir", "/Users/your-name/Desktop/dataset",
109
+ ]
110
+ tool_timeout_sec = 600
111
+ ```
112
+
113
+ 등록 후 `codex mcp list` 또는 Codex의 `/mcp` 메뉴에서 연결 상태를 확인합니다.
114
+
115
+ ## 4. Claude Code 연결
116
+
117
+ ```bash
118
+ claude mcp add hwp-rag -- \
119
+ uvx --from hwp-rag-mcp hwp-rag-mcp serve \
120
+ --dataset-dir ~/Desktop/dataset
121
+ ```
122
+
123
+ 프로젝트 단위 설정이 필요하면 Claude Code의 `--scope project` 옵션을 함께 사용합니다.
124
+
125
+ ## 5. MCP 도구
126
+
127
+ | 도구 | 용도 |
128
+ | --- | --- |
129
+ | `get_index_status` | 인덱스가 `missing`, `current`, `stale`인지 확인 |
130
+ | `sync_index` | 파일 변경 후 전체 인덱스를 명시적으로 재구축 |
131
+ | `list_documents` | 마지막 유효 인덱스에 포함된 문서 목록 확인 |
132
+ | `search_documents` | 질문과 유사한 근거 청크 검색 |
133
+
134
+ 예시 요청:
135
+
136
+ ```text
137
+ HWP 문서에서 연차휴가 신청 조건을 검색하고, 근거 파일명을 함께 표시해 줘.
138
+ ```
139
+
140
+ `search_documents`는 최대 20개 결과를 반환하며, 정확한 파일명 목록으로 검색 범위를 제한할
141
+ 수 있습니다. 파일이 변경되면 오래된 인덱스로 검색하지 않고 먼저 `sync_index`를 요구합니다.
142
+
143
+ ## 인덱스와 개인정보
144
+
145
+ - FAISS 인덱스는 운영체제별 사용자 데이터 디렉터리에 저장됩니다.
146
+ - 데이터셋 절대경로의 SHA-256 일부를 저장 폴더 키로 사용합니다.
147
+ - `index.faiss`, `documents.json`, `manifest.json`만 저장하며 pickle 역직렬화는 사용하지
148
+ 않습니다.
149
+ - manifest에는 파일 크기, 수정 시각, SHA-256과 모델·분할 설정이 기록됩니다.
150
+ - MCP 도구는 서버 시작 시 지정한 데이터셋 밖의 파일 경로를 입력받지 않습니다.
151
+ - API 키를 텍스트 파일로 저장하거나 읽는 기능은 없습니다.
152
+
153
+ ## 문제 해결
154
+
155
+ ### `index_missing`
156
+
157
+ 데이터셋 경로와 파일 확장자를 확인한 뒤 CLI의 `sync`를 실행합니다.
158
+
159
+ ### `index_stale`
160
+
161
+ 파일이 추가·수정·삭제되었거나 분할 설정이 달라졌습니다. `sync` 또는 MCP의
162
+ `sync_index`를 실행합니다.
163
+
164
+ ### 최초 동기화가 MCP에서 시간 초과됨
165
+
166
+ 모델 다운로드가 포함된 최초 동기화는 터미널의 `hwp-rag-mcp sync`로 먼저 완료합니다.
167
+ Codex 설정에서는 필요하면 `tool_timeout_sec`를 늘립니다.
168
+
169
+ ### 암호화 또는 손상 문서
170
+
171
+ 해당 파일은 동기화 보고서의 `skipped_files` 또는 `failed_files`에 표시되며, 다른 정상
172
+ 문서는 계속 색인됩니다.
173
+
174
+ ## 개발
175
+
176
+ ```bash
177
+ python -m venv .venv
178
+ source .venv/bin/activate
179
+ python -m pip install -e '.[dev]'
180
+ ruff check .
181
+ pytest
182
+ python -m build
183
+ twine check dist/*
184
+ ```
185
+
186
+ ## English summary
187
+
188
+ HWP RAG MCP builds a local, explicitly synchronized FAISS index from Korean HWP/HWPX files and
189
+ exposes evidence retrieval over MCP. It uses a local multilingual E5 model, requires no embedding API
190
+ key, and leaves answer generation to Codex or Claude Code.
191
+
@@ -0,0 +1,152 @@
1
+ # HWP RAG MCP
2
+
3
+ `hwp-rag-mcp`는 바탕화면의 `.hwp`/`.hwpx` 문서를 로컬에서 파싱하고 검색할 수 있게
4
+ 해 주는 MCP 서버입니다. 문서는 로컬 E5 임베딩과 FAISS로 색인되며, Codex 또는 Claude
5
+ Code가 검색 근거를 받아 최종 답변을 작성합니다.
6
+
7
+ - 외부 문서 API나 OpenAI API 키가 필요하지 않습니다.
8
+ - 문서 내용과 벡터 인덱스는 사용자 컴퓨터에만 저장됩니다.
9
+ - 표, 각주, 미주, 메모의 LangChain 메타데이터를 유지합니다.
10
+ - 파일 변경 후 사용자가 명시적으로 동기화해야 하므로 검색 동작이 예측 가능합니다.
11
+
12
+ > 현재 버전은 개인용·소규모 데모 문서함을 대상으로 합니다. OCR, 이미지 검색, 자동 파일
13
+ > 감시, 원격 MCP 전송, 서버 내부 답변 생성은 지원하지 않습니다.
14
+
15
+ ## 1. 준비
16
+
17
+ Python 3.10~3.13과 [uv](https://docs.astral.sh/uv/) 사용을 권장합니다.
18
+
19
+ ```bash
20
+ mkdir -p ~/Desktop/dataset
21
+ ```
22
+
23
+ 검색할 HWP/HWPX 파일을 `~/Desktop/dataset`에 넣습니다. 하위 폴더도 재귀적으로
24
+ 검색합니다. 심볼릭 링크와 다른 확장자는 무시합니다.
25
+
26
+ ## 2. 최초 인덱스 생성
27
+
28
+ ```bash
29
+ uvx --from hwp-rag-mcp hwp-rag-mcp sync \
30
+ --dataset-dir ~/Desktop/dataset
31
+ ```
32
+
33
+ 최초 실행에서는 `intfloat/multilingual-e5-small` 모델과 로컬 추론 의존성을 다운로드하므로
34
+ 시간과 디스크 공간이 필요합니다. 이후 모델 캐시와 인덱스가 유지되는 동안에는 오프라인
35
+ 검색이 가능합니다.
36
+
37
+ 상태 확인과 강제 재구축:
38
+
39
+ ```bash
40
+ uvx --from hwp-rag-mcp hwp-rag-mcp status \
41
+ --dataset-dir ~/Desktop/dataset
42
+
43
+ uvx --from hwp-rag-mcp hwp-rag-mcp sync \
44
+ --dataset-dir ~/Desktop/dataset --force
45
+ ```
46
+
47
+ `--dataset-dir`를 생략하면 `HWP_RAG_DATASET_DIR` 환경변수, 그다음
48
+ `~/Desktop/dataset`을 사용합니다.
49
+
50
+ ## 3. Codex 연결
51
+
52
+ CLI에서 로컬 STDIO MCP 서버를 등록합니다.
53
+
54
+ ```bash
55
+ codex mcp add hwp-rag -- \
56
+ uvx --from hwp-rag-mcp hwp-rag-mcp serve \
57
+ --dataset-dir ~/Desktop/dataset
58
+ ```
59
+
60
+ 직접 설정할 경우 `~/.codex/config.toml`에 다음을 추가할 수 있습니다. MCP 프로세스가 셸을
61
+ 거치지 않는 환경을 고려하면 절대경로가 가장 안전합니다.
62
+
63
+ ```toml
64
+ [mcp_servers.hwp-rag]
65
+ command = "uvx"
66
+ args = [
67
+ "--from", "hwp-rag-mcp",
68
+ "hwp-rag-mcp", "serve",
69
+ "--dataset-dir", "/Users/your-name/Desktop/dataset",
70
+ ]
71
+ tool_timeout_sec = 600
72
+ ```
73
+
74
+ 등록 후 `codex mcp list` 또는 Codex의 `/mcp` 메뉴에서 연결 상태를 확인합니다.
75
+
76
+ ## 4. Claude Code 연결
77
+
78
+ ```bash
79
+ claude mcp add hwp-rag -- \
80
+ uvx --from hwp-rag-mcp hwp-rag-mcp serve \
81
+ --dataset-dir ~/Desktop/dataset
82
+ ```
83
+
84
+ 프로젝트 단위 설정이 필요하면 Claude Code의 `--scope project` 옵션을 함께 사용합니다.
85
+
86
+ ## 5. MCP 도구
87
+
88
+ | 도구 | 용도 |
89
+ | --- | --- |
90
+ | `get_index_status` | 인덱스가 `missing`, `current`, `stale`인지 확인 |
91
+ | `sync_index` | 파일 변경 후 전체 인덱스를 명시적으로 재구축 |
92
+ | `list_documents` | 마지막 유효 인덱스에 포함된 문서 목록 확인 |
93
+ | `search_documents` | 질문과 유사한 근거 청크 검색 |
94
+
95
+ 예시 요청:
96
+
97
+ ```text
98
+ HWP 문서에서 연차휴가 신청 조건을 검색하고, 근거 파일명을 함께 표시해 줘.
99
+ ```
100
+
101
+ `search_documents`는 최대 20개 결과를 반환하며, 정확한 파일명 목록으로 검색 범위를 제한할
102
+ 수 있습니다. 파일이 변경되면 오래된 인덱스로 검색하지 않고 먼저 `sync_index`를 요구합니다.
103
+
104
+ ## 인덱스와 개인정보
105
+
106
+ - FAISS 인덱스는 운영체제별 사용자 데이터 디렉터리에 저장됩니다.
107
+ - 데이터셋 절대경로의 SHA-256 일부를 저장 폴더 키로 사용합니다.
108
+ - `index.faiss`, `documents.json`, `manifest.json`만 저장하며 pickle 역직렬화는 사용하지
109
+ 않습니다.
110
+ - manifest에는 파일 크기, 수정 시각, SHA-256과 모델·분할 설정이 기록됩니다.
111
+ - MCP 도구는 서버 시작 시 지정한 데이터셋 밖의 파일 경로를 입력받지 않습니다.
112
+ - API 키를 텍스트 파일로 저장하거나 읽는 기능은 없습니다.
113
+
114
+ ## 문제 해결
115
+
116
+ ### `index_missing`
117
+
118
+ 데이터셋 경로와 파일 확장자를 확인한 뒤 CLI의 `sync`를 실행합니다.
119
+
120
+ ### `index_stale`
121
+
122
+ 파일이 추가·수정·삭제되었거나 분할 설정이 달라졌습니다. `sync` 또는 MCP의
123
+ `sync_index`를 실행합니다.
124
+
125
+ ### 최초 동기화가 MCP에서 시간 초과됨
126
+
127
+ 모델 다운로드가 포함된 최초 동기화는 터미널의 `hwp-rag-mcp sync`로 먼저 완료합니다.
128
+ Codex 설정에서는 필요하면 `tool_timeout_sec`를 늘립니다.
129
+
130
+ ### 암호화 또는 손상 문서
131
+
132
+ 해당 파일은 동기화 보고서의 `skipped_files` 또는 `failed_files`에 표시되며, 다른 정상
133
+ 문서는 계속 색인됩니다.
134
+
135
+ ## 개발
136
+
137
+ ```bash
138
+ python -m venv .venv
139
+ source .venv/bin/activate
140
+ python -m pip install -e '.[dev]'
141
+ ruff check .
142
+ pytest
143
+ python -m build
144
+ twine check dist/*
145
+ ```
146
+
147
+ ## English summary
148
+
149
+ HWP RAG MCP builds a local, explicitly synchronized FAISS index from Korean HWP/HWPX files and
150
+ exposes evidence retrieval over MCP. It uses a local multilingual E5 model, requires no embedding API
151
+ key, and leaves answer generation to Codex or Claude Code.
152
+
@@ -0,0 +1,87 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.25"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "hwp-rag-mcp"
7
+ version = "0.1.0"
8
+ description = "Local HWP/HWPX retrieval for Codex and Claude Code over MCP"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10,<3.14"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [
14
+ { name = "HWP RAG MCP Contributors" },
15
+ ]
16
+ keywords = ["hwp", "hwpx", "rag", "mcp", "faiss", "langchain", "korean"]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Topic :: Text Processing :: Indexing",
28
+ ]
29
+ dependencies = [
30
+ "faiss-cpu>=1.12,<2",
31
+ "langchain-community>=0.4,<1",
32
+ "langchain-huggingface>=1,<2",
33
+ "langchain-hwp-hwpx-loader>=0.1.1,<0.2",
34
+ "langchain-text-splitters>=1,<2",
35
+ "mcp>=1.20,<2",
36
+ "platformdirs>=4,<5",
37
+ "sentence-transformers>=5,<6",
38
+ ]
39
+
40
+ [project.optional-dependencies]
41
+ dev = [
42
+ "build>=1.2",
43
+ "mypy>=1.11",
44
+ "pytest>=8",
45
+ "pytest-asyncio>=0.24",
46
+ "ruff>=0.7",
47
+ "twine>=5",
48
+ ]
49
+
50
+ [project.scripts]
51
+ hwp-rag-mcp = "hwp_rag_mcp.cli:main"
52
+
53
+ [project.urls]
54
+ Homepage = "https://github.com/jaypakdevkr/HWP-RAG-MCP"
55
+ Repository = "https://github.com/jaypakdevkr/HWP-RAG-MCP"
56
+ Issues = "https://github.com/jaypakdevkr/HWP-RAG-MCP/issues"
57
+
58
+ [tool.hatch.build.targets.wheel]
59
+ packages = ["src/hwp_rag_mcp"]
60
+
61
+ [tool.hatch.build.targets.sdist]
62
+ include = [
63
+ "/src",
64
+ "/tests",
65
+ "/README.md",
66
+ "/LICENSE",
67
+ "/CHANGELOG.md",
68
+ "/pyproject.toml",
69
+ ]
70
+
71
+ [tool.pytest.ini_options]
72
+ testpaths = ["tests"]
73
+ addopts = "-ra"
74
+ asyncio_mode = "auto"
75
+
76
+ [tool.ruff]
77
+ line-length = 100
78
+ target-version = "py310"
79
+
80
+ [tool.ruff.lint]
81
+ select = ["E", "F", "I", "UP", "B", "SIM"]
82
+
83
+ [tool.mypy]
84
+ python_version = "3.12"
85
+ strict = true
86
+ packages = ["hwp_rag_mcp"]
87
+ warn_unused_ignores = false
@@ -0,0 +1,35 @@
1
+ """Local HWP/HWPX retrieval over the Model Context Protocol."""
2
+
3
+ import os
4
+ from importlib.metadata import PackageNotFoundError, version
5
+
6
+ # Set this before importing FAISS/LangChain modules, which may transitively import
7
+ # Hugging Face tokenizers. Parallel tokenizers are unnecessary for this local STDIO
8
+ # process and have caused shutdown crashes on some macOS ARM Python builds.
9
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
10
+
11
+ try:
12
+ __version__ = version("hwp-rag-mcp")
13
+ except PackageNotFoundError: # pragma: no cover - source tree without installation
14
+ __version__ = "0.1.0"
15
+
16
+ from .index import IndexManager
17
+ from .models import (
18
+ DocumentSummary,
19
+ IndexStatus,
20
+ SearchResponse,
21
+ SearchResult,
22
+ SyncFailure,
23
+ SyncReport,
24
+ )
25
+
26
+ __all__ = [
27
+ "DocumentSummary",
28
+ "IndexManager",
29
+ "IndexStatus",
30
+ "SearchResponse",
31
+ "SearchResult",
32
+ "SyncFailure",
33
+ "SyncReport",
34
+ "__version__",
35
+ ]
@@ -0,0 +1,6 @@
1
+ """Module entry point for ``python -m hwp_rag_mcp``."""
2
+
3
+ from .cli import main
4
+
5
+ raise SystemExit(main())
6
+
@@ -0,0 +1,67 @@
1
+ """Command-line interface for status, synchronization, and MCP serving."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from collections.abc import Sequence
8
+ from pathlib import Path
9
+
10
+ from . import __version__
11
+ from .index import IndexManager
12
+
13
+
14
+ def _add_dataset_argument(parser: argparse.ArgumentParser) -> None:
15
+ parser.add_argument(
16
+ "--dataset-dir",
17
+ type=Path,
18
+ default=None,
19
+ help="HWP/HWPX folder (CLI > HWP_RAG_DATASET_DIR > ~/Desktop/dataset)",
20
+ )
21
+
22
+
23
+ def build_parser() -> argparse.ArgumentParser:
24
+ parser = argparse.ArgumentParser(
25
+ prog="hwp-rag-mcp",
26
+ description="Build and serve a local HWP/HWPX FAISS retrieval index.",
27
+ )
28
+ parser.add_argument("--version", action="version", version=__version__)
29
+ subparsers = parser.add_subparsers(dest="command", required=True)
30
+
31
+ status_parser = subparsers.add_parser("status", help="Check index freshness")
32
+ _add_dataset_argument(status_parser)
33
+
34
+ sync_parser = subparsers.add_parser("sync", help="Explicitly rebuild the index")
35
+ _add_dataset_argument(sync_parser)
36
+ sync_parser.add_argument("--force", action="store_true", help="Rebuild a current index")
37
+
38
+ serve_parser = subparsers.add_parser("serve", help="Run the MCP STDIO server")
39
+ _add_dataset_argument(serve_parser)
40
+ return parser
41
+
42
+
43
+ def main(argv: Sequence[str] | None = None) -> int:
44
+ parser = build_parser()
45
+ args = parser.parse_args(argv)
46
+
47
+ if args.command == "serve":
48
+ from .server import run_server
49
+
50
+ run_server(args.dataset_dir)
51
+ return 0
52
+
53
+ try:
54
+ manager = IndexManager(args.dataset_dir)
55
+ if args.command == "status":
56
+ print(manager.status().model_dump_json(indent=2))
57
+ return 0
58
+ if args.command == "sync":
59
+ report = manager.sync(force=args.force)
60
+ print(report.model_dump_json(indent=2))
61
+ return 0 if report.state == "current" else 1
62
+ except Exception as exc:
63
+ print(f"{exc.__class__.__name__}: {exc}", file=sys.stderr)
64
+ return 1
65
+ parser.error(f"Unsupported command: {args.command}")
66
+ return 2
67
+
@@ -0,0 +1,73 @@
1
+ """Configuration and path resolution for the local RAG service."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import os
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+
10
+ from platformdirs import user_data_path
11
+
12
+ DEFAULT_MODEL_NAME = "intfloat/multilingual-e5-small"
13
+ DEFAULT_CHUNK_SIZE = 1_000
14
+ DEFAULT_CHUNK_OVERLAP = 150
15
+ DATASET_ENV_VAR = "HWP_RAG_DATASET_DIR"
16
+ STORAGE_ENV_VAR = "HWP_RAG_STORAGE_DIR"
17
+
18
+
19
+ def normalize_path(value: str | Path) -> Path:
20
+ """Expand and resolve a user-provided path without requiring it to exist."""
21
+
22
+ return Path(value).expanduser().resolve(strict=False)
23
+
24
+
25
+ def resolve_dataset_dir(value: str | Path | None = None) -> Path:
26
+ """Resolve dataset path using CLI value, environment, then Desktop default."""
27
+
28
+ if value is not None:
29
+ return normalize_path(value)
30
+ environment_value = os.getenv(DATASET_ENV_VAR)
31
+ if environment_value:
32
+ return normalize_path(environment_value)
33
+ return normalize_path(Path.home() / "Desktop" / "dataset")
34
+
35
+
36
+ def resolve_storage_root(value: str | Path | None = None) -> Path:
37
+ """Resolve the private application data directory used for indexes."""
38
+
39
+ if value is not None:
40
+ return normalize_path(value)
41
+ environment_value = os.getenv(STORAGE_ENV_VAR)
42
+ if environment_value:
43
+ return normalize_path(environment_value)
44
+ return normalize_path(user_data_path("hwp-rag-mcp", appauthor=False))
45
+
46
+
47
+ def dataset_storage_key(dataset_dir: Path) -> str:
48
+ """Create a stable, non-sensitive storage key for an absolute dataset path."""
49
+
50
+ digest = hashlib.sha256(str(dataset_dir).encode("utf-8")).hexdigest()[:16]
51
+ return f"dataset-{digest}"
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class IndexConfig:
56
+ """Immutable settings that affect the generated vector index."""
57
+
58
+ dataset_dir: Path
59
+ storage_root: Path
60
+ model_name: str = DEFAULT_MODEL_NAME
61
+ chunk_size: int = DEFAULT_CHUNK_SIZE
62
+ chunk_overlap: int = DEFAULT_CHUNK_OVERLAP
63
+
64
+ def __post_init__(self) -> None:
65
+ if self.chunk_size <= 0:
66
+ raise ValueError("chunk_size must be greater than zero")
67
+ if self.chunk_overlap < 0 or self.chunk_overlap >= self.chunk_size:
68
+ raise ValueError("chunk_overlap must be non-negative and smaller than chunk_size")
69
+
70
+ @property
71
+ def index_dir(self) -> Path:
72
+ return self.storage_root / dataset_storage_key(self.dataset_dir)
73
+
@@ -0,0 +1,26 @@
1
+ """Local multilingual embedding construction."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+ from langchain_core.embeddings import Embeddings
8
+
9
+ from .config import DEFAULT_MODEL_NAME
10
+
11
+
12
+ def create_local_embeddings(model_name: str = DEFAULT_MODEL_NAME) -> Embeddings:
13
+ """Create the supported local E5 embedder with retrieval prompts enabled."""
14
+
15
+ # Hugging Face tokenizers can initialize worker resources that are unnecessary for
16
+ # this single-process STDIO server and unstable in some macOS ARM Python builds.
17
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
18
+
19
+ from langchain_huggingface import HuggingFaceEmbeddings
20
+
21
+ return HuggingFaceEmbeddings(
22
+ model_name=model_name,
23
+ model_kwargs={"device": "cpu"},
24
+ encode_kwargs={"normalize_embeddings": True, "prompt": "passage: "},
25
+ query_encode_kwargs={"normalize_embeddings": True, "prompt": "query: "},
26
+ )