@kimdayoun/hwpx-mcp 0.3.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 mjyoo
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.
package/README.md ADDED
@@ -0,0 +1,356 @@
1
+ # HWPX MCP Server — `@dayoun/hwpx-mcp`
2
+
3
+ [![npm](https://img.shields.io/npm/v/@dayoun/hwpx-mcp)](https://www.npmjs.com/package/@dayoun/hwpx-mcp)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![MCP](https://img.shields.io/badge/MCP-Compatible-blue)](https://modelcontextprotocol.io/)
6
+
7
+ > **이 저장소가 원본입니다.** 정본: https://github.com/Dayoooun/hwpx-mcp
8
+ > npm 배포본은 **`@dayoun/hwpx-mcp`** 하나뿐입니다.
9
+ > `hwpx-mcp`, `hwpx-mcp-server` 등 스코프 없는 동명 패키지는 이 프로젝트와 무관한 제3자 배포본입니다.
10
+
11
+ HWP/HWPX 문서를 AI로 읽고 편집할 수 있는 Model Context Protocol (MCP) 서버입니다.
12
+
13
+ ## 특징
14
+
15
+ - **125개 도구**: 문서의 모든 요소를 프로그래밍 방식으로 제어
16
+ - **HWPX 완전 편집**: 텍스트, 테이블, 이미지, 스타일, 머리글/꼬리글 등
17
+ - **HWP 읽기 지원**: 레거시 HWP 바이너리 포맷 읽기
18
+ - **XML 무결성 보장**: 모든 편집이 HWPML 규격에 맞게 XML에 저장
19
+ - **24개 E2E 테스트 통과**: save-reload 검증 완료
20
+
21
+ ## 설치
22
+
23
+ ```bash
24
+ npm install -g @dayoun/hwpx-mcp
25
+ ```
26
+
27
+ 설치 없이 바로 실행하려면:
28
+
29
+ ```bash
30
+ npx -y @dayoun/hwpx-mcp
31
+ ```
32
+
33
+ 소스에서 빌드하려면:
34
+
35
+ ```bash
36
+ git clone https://github.com/Dayoooun/hwpx-mcp.git
37
+ cd hwpx-mcp/mcp-server
38
+ npm install
39
+ npm run build
40
+ ```
41
+
42
+ ## 설정
43
+
44
+ ### Claude Code (.vscode/mcp.json)
45
+
46
+ 프로젝트 루트에 `.vscode/mcp.json` 파일 생성:
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "hwpx-mcp": {
52
+ "command": "npx",
53
+ "args": ["-y", "@dayoun/hwpx-mcp"]
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ ### Claude Desktop (claude_desktop_config.json)
60
+
61
+ **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
62
+ **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
63
+
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "hwpx-mcp": {
68
+ "command": "npx",
69
+ "args": ["-y", "@dayoun/hwpx-mcp"]
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ ## 도구 목록 (125개)
76
+
77
+ ### 문서 관리 (7개)
78
+ | 도구 | 설명 |
79
+ |------|------|
80
+ | `get_tool_guide` | 도구 가이드 조회 |
81
+ | `open_document` | 문서 열기 (HWPX/HWP) |
82
+ | `create_document` | 새 HWPX 문서 생성 |
83
+ | `save_document` | 문서 저장 |
84
+ | `close_document` | 문서 닫기 |
85
+ | `list_open_documents` | 열린 문서 목록 |
86
+ | `get_document_metadata` | 메타데이터 조회 |
87
+ | `set_document_metadata` | 메타데이터 설정 |
88
+
89
+ ### 문서 조회 (9개)
90
+ | 도구 | 설명 |
91
+ |------|------|
92
+ | `get_document_text` | 전체 텍스트 |
93
+ | `get_document_structure` | 문서 구조 |
94
+ | `get_document_outline` | 문서 개요 |
95
+ | `get_paragraphs` | 문단 목록 |
96
+ | `get_paragraph` | 특정 문단 상세 |
97
+ | `get_word_count` | 단어 수 |
98
+ | `get_insert_context` | 삽입 컨텍스트 |
99
+ | `find_insert_position_after_header` | 헤더 다음 위치 찾기 |
100
+ | `find_insert_position_after_table` | 테이블 다음 위치 찾기 |
101
+
102
+ ### 텍스트 편집 (11개)
103
+ | 도구 | 설명 |
104
+ |------|------|
105
+ | `insert_paragraph` | 문단 삽입 |
106
+ | `update_paragraph_text` | 문단 텍스트 수정 |
107
+ | `update_paragraph_text_preserve_styles` | 스타일 유지 텍스트 수정 |
108
+ | `append_text_to_paragraph` | 문단에 텍스트 추가 |
109
+ | `delete_paragraph` | 문단 삭제 |
110
+ | `copy_paragraph` | 문단 복사 |
111
+ | `move_paragraph` | 문단 이동 |
112
+ | `search_text` | 텍스트 검색 |
113
+ | `replace_text` | 텍스트 치환 |
114
+ | `batch_replace` | 일괄 치환 |
115
+ | `find_paragraph_by_text` | 텍스트로 문단 찾기 |
116
+
117
+ ### 서식 (스타일) (9개)
118
+ | 도구 | 설명 |
119
+ |------|------|
120
+ | `get_text_style` | 문자 스타일 조회 |
121
+ | `set_text_style` | 문자 스타일 설정 (폰트, 크기, 볼드 등) |
122
+ | `get_paragraph_style` | 문단 스타일 조회 |
123
+ | `set_paragraph_style` | 문단 스타일 설정 (정렬, 줄간격 등) |
124
+ | `get_styles` | 스타일 목록 |
125
+ | `get_char_shapes` | 문자 모양 전체 |
126
+ | `get_para_shapes` | 문단 모양 전체 |
127
+ | `apply_style` | 스타일 적용 |
128
+ | `get_column_def` | 단 설정 조회 |
129
+ | `set_column_def` | 단 설정 |
130
+
131
+ ### 내어쓰기 (8개)
132
+ | 도구 | 설명 |
133
+ |------|------|
134
+ | `get_hanging_indent` | 내어쓰기 조회 |
135
+ | `set_hanging_indent` | 내어쓰기 설정 |
136
+ | `set_auto_hanging_indent` | 자동 내어쓰기 |
137
+ | `remove_hanging_indent` | 내어쓰기 제거 |
138
+ | `get_table_cell_hanging_indent` | 셀 내어쓰기 조회 |
139
+ | `set_table_cell_hanging_indent` | 셀 내어쓰기 설정 |
140
+ | `set_table_cell_auto_hanging_indent` | 셀 자동 내어쓰기 |
141
+ | `remove_table_cell_hanging_indent` | 셀 내어쓰기 제거 |
142
+
143
+ ### 테이블 (23개)
144
+ | 도구 | 설명 |
145
+ |------|------|
146
+ | `get_tables` | 테이블 목록 |
147
+ | `get_table` | 테이블 상세 |
148
+ | `get_table_cell` | 셀 읽기 |
149
+ | `get_table_as_csv` | CSV 내보내기 |
150
+ | `get_table_map` | 테이블 맵 |
151
+ | `get_tables_summary` | 테이블 요약 |
152
+ | `get_tables_by_section` | 섹션별 테이블 |
153
+ | `find_table_by_header` | 헤더로 테이블 찾기 |
154
+ | `find_empty_tables` | 빈 테이블 찾기 |
155
+ | `get_element_index_for_table` | 요소 인덱스 |
156
+ | `insert_table` | 테이블 삽입 |
157
+ | `insert_nested_table` | 중첩 테이블 |
158
+ | `delete_table` | 테이블 삭제 |
159
+ | `update_table_cell` | 셀 수정 |
160
+ | `replace_text_in_cell` | 셀 텍스트 치환 |
161
+ | `set_cell_properties` | 셀 속성 |
162
+ | `insert_table_row` | 행 추가 |
163
+ | `delete_table_row` | 행 삭제 |
164
+ | `insert_table_column` | 열 추가 |
165
+ | `delete_table_column` | 열 삭제 |
166
+ | `merge_cells` | 셀 병합 |
167
+ | `split_cell` | 셀 분할 |
168
+ | `copy_table` | 테이블 복사 |
169
+ | `move_table` | 테이블 이동 |
170
+
171
+ ### 머리글/꼬리글/각주 (8개)
172
+ | 도구 | 설명 |
173
+ |------|------|
174
+ | `get_header` | 머리글 조회 |
175
+ | `set_header` | 머리글 설정 |
176
+ | `get_footer` | 꼬리글 조회 |
177
+ | `set_footer` | 꼬리글 설정 |
178
+ | `get_footnotes` | 각주 목록 |
179
+ | `insert_footnote` | 각주 삽입 |
180
+ | `get_endnotes` | 미주 목록 |
181
+ | `insert_endnote` | 미주 삽입 |
182
+
183
+ ### 이미지 (7개)
184
+ | 도구 | 설명 |
185
+ |------|------|
186
+ | `get_images` | 이미지 목록 |
187
+ | `insert_image` | 이미지 삽입 |
188
+ | `update_image_size` | 이미지 크기 변경 |
189
+ | `delete_image` | 이미지 삭제 |
190
+ | `render_mermaid` | Mermaid 다이어그램 삽입 |
191
+ | `insert_image_in_cell` | 셀에 이미지 삽입 |
192
+ | `render_mermaid_in_cell` | 셀에 Mermaid 삽입 |
193
+
194
+ ### 북마크/하이퍼링크 (4개)
195
+ | 도구 | 설명 |
196
+ |------|------|
197
+ | `get_bookmarks` | 북마크 목록 |
198
+ | `insert_bookmark` | 북마크 삽입 |
199
+ | `get_hyperlinks` | 하이퍼링크 목록 |
200
+ | `insert_hyperlink` | 하이퍼링크 삽입 |
201
+
202
+ ### 수식/메모 (6개)
203
+ | 도구 | 설명 |
204
+ |------|------|
205
+ | `get_equations` | 수식 목록 |
206
+ | `insert_equation` | 수식 삽입 |
207
+ | `get_memos` | 메모 목록 |
208
+ | `insert_memo` | 메모 삽입 |
209
+ | `delete_memo` | 메모 삭제 |
210
+
211
+ ### 도형 (3개)
212
+ | 도구 | 설명 |
213
+ |------|------|
214
+ | `insert_line` | 선 삽입 |
215
+ | `insert_rect` | 사각형 삽입 |
216
+ | `insert_ellipse` | 타원 삽입 |
217
+
218
+ ### 섹션/페이지 (5개)
219
+ | 도구 | 설명 |
220
+ |------|------|
221
+ | `get_sections` | 섹션 목록 |
222
+ | `insert_section` | 섹션 삽입 |
223
+ | `delete_section` | 섹션 삭제 |
224
+ | `get_page_settings` | 페이지 설정 조회 |
225
+ | `set_page_settings` | 페이지 설정 |
226
+
227
+ ### 실행 취소/다시 실행 (2개)
228
+ | 도구 | 설명 |
229
+ |------|------|
230
+ | `undo` | 실행 취소 |
231
+ | `redo` | 다시 실행 |
232
+
233
+ ### 내보내기 (2개)
234
+ | 도구 | 설명 |
235
+ |------|------|
236
+ | `export_to_text` | TXT 내보내기 |
237
+ | `export_to_html` | HTML 내보내기 |
238
+
239
+ ### 고급/디버깅 (8개)
240
+ | 도구 | 설명 |
241
+ |------|------|
242
+ | `get_section_xml` | 섹션 XML 조회 |
243
+ | `set_section_xml` | 섹션 XML 설정 |
244
+ | `get_raw_section_xml` | 원본 섹션 XML |
245
+ | `set_raw_section_xml` | 원본 섹션 XML 설정 |
246
+ | `analyze_xml` | XML 분석 |
247
+ | `repair_xml` | XML 복구 |
248
+ | `chunk_document` | 문서 청킹 |
249
+ | `invalidate_reading_cache` | 읽기 캐시 무효화 |
250
+
251
+ ### 검색/인덱싱 (5개)
252
+ | 도구 | 설명 |
253
+ |------|------|
254
+ | `search_chunks` | 청크 검색 |
255
+ | `get_chunk_context` | 청크 컨텍스트 |
256
+ | `extract_toc` | 목차 추출 |
257
+ | `build_position_index` | 위치 인덱스 빌드 |
258
+ | `get_position_index` | 위치 인덱스 조회 |
259
+ | `search_position_index` | 위치 인덱스 검색 |
260
+ | `get_chunk_at_offset` | 오프셋 청크 조회 |
261
+
262
+ ## 사용 예시
263
+
264
+ ### 예시 1: 테이블 편집
265
+ ```
266
+ 사용자: 사업계획서.hwpx를 열어서 3번째 테이블의 2행 1열을 수정해줘
267
+
268
+ AI 동작:
269
+ 1. open_document("사업계획서.hwpx")
270
+ 2. get_tables() → 테이블 목록 확인
271
+ 3. get_table(section=0, index=2) → 구조 확인
272
+ 4. update_table_cell(section=0, table=2, row=1, col=0, text="수정 내용")
273
+ 5. save_document()
274
+ ```
275
+
276
+ ### 예시 2: 스타일 복사
277
+ ```
278
+ 사용자: 기존 양식의 스타일을 참고해서 새 문단을 추가해줘
279
+
280
+ AI 동작:
281
+ 1. get_paragraph_style(sec=0, para=10) → {align: "Justify", lineSpacing: 145}
282
+ 2. get_text_style(sec=0, para=10) → {fontName: "맑은 고딕", fontSize: 14}
283
+ 3. insert_paragraph(sec=0, after=10, text="새 내용")
284
+ 4. set_paragraph_style(sec=0, para=11, align="justify", line_spacing=145)
285
+ 5. set_text_style(sec=0, para=11, font_name="맑은 고딕", font_size=14)
286
+ 6. save_document()
287
+ ```
288
+
289
+ ### 예시 3: 자동 내어쓰기
290
+ ```
291
+ 사용자: 테이블 1행 1열에 "1. 항목\n2. 항목" 넣고 자동 내어쓰기 적용해줘
292
+
293
+ AI 동작:
294
+ 1. update_table_cell(section=0, table=0, row=0, col=0, text="1. 항목\n2. 항목")
295
+ 2. set_table_cell_auto_hanging_indent(section=0, table=0, row=0, col=0)
296
+ 3. save_document()
297
+ ```
298
+
299
+ ## 테스트
300
+
301
+ ```bash
302
+ cd mcp-server
303
+ npm test # vitest 단위 테스트
304
+ node test-mcp-e2e.mjs # 16개 기본 E2E 테스트
305
+ node test-new-persistence-e2e.mjs # 8개 persistence E2E 테스트
306
+ ```
307
+
308
+ ## 알려진 제한사항
309
+
310
+ - **각주/미주/북마크/하이퍼링크 삽입**: 메모리에서만 동작, save 후 XML 미반영 (읽기는 정상)
311
+ - **HWP 파일**: 읽기 전용 (편집 불가)
312
+ - **secPr 문단 스타일**: 첫 번째 특수 문단에서 스타일 reload 제한
313
+
314
+ ## 변경 이력
315
+
316
+ ### 0.3.0 (2026-01-28)
317
+ - **대규모 XML persistence 수정**: 8개 조작에 대한 save-reload 지원 추가
318
+ - `insertTableRow` / `deleteTableRow`
319
+ - `insertTableColumn` / `deleteTableColumn`
320
+ - `copyParagraph` / `moveParagraph` (+ 같은 섹션 인덱스 버그 수정)
321
+ - `setHeader` / `setFooter`
322
+ - **depth-aware element indexing**: 중첩 태그 내부 요소를 무시하는 안전한 파싱
323
+ - **undo/redo 안전성**: pending 배열 초기화로 메모리/XML 비동기화 방지
324
+ - **replaceText 수정**: XML entity 불일치 해결
325
+ - **mergeCells 수정**: indexOf 모호성 해결
326
+ - **테스트**: 24개 E2E 테스트 (16 기본 + 8 persistence)
327
+
328
+ ### 0.2.0
329
+ - **신규 기능**: 테이블 셀 내 내어쓰기(Hanging Indent) 자동 적용
330
+ - `update_table_cell` 시 마커(○, 1., 가., (1) 등) 감지하여 자동 내어쓰기
331
+ - 멀티라인 텍스트의 각 줄에 독립적으로 내어쓰기 적용
332
+ - `set_table_cell_hanging_indent`, `get_table_cell_hanging_indent` 도구 추가
333
+ - **버그 수정**: 병렬 테이블 업데이트 시 XML 손상 문제 해결
334
+ - 문서별 Lock 추가로 병렬 요청 직렬화 (race condition 방지)
335
+ - `findTableCellInXml()` 중첩 테이블 처리 개선 (balanced bracket 매칭)
336
+ - 여러 테이블 동시 수정 후 저장 시 "Broken tag structure" 오류 수정
337
+ - **버그 수정**: 여러 테이블에 내어쓰기 적용 시 stale position 문제 해결
338
+ - 테이블 인덱스 내림차순 처리로 위치 변경 영향 방지
339
+ - 각 테이블 처리 시 위치 정보 재계산
340
+ - **테스트 강화**: Red Team 스트레스 테스트 추가 (238개 테스트)
341
+ - 50~200개 테이블 대량 수정 테스트
342
+ - 중첩 테이블 + 내어쓰기 + 이미지 복합 테스트
343
+ - 병렬 업데이트 시나리오 테스트
344
+
345
+ ### 0.1.1
346
+ - **버그 수정**: `update_table_cell` 후 `save_document` 시 빈 셀 변경사항이 저장되지 않던 문제 수정
347
+ - Self-closing XML run 태그 (`<hp:run ... />`) 처리 지원 추가
348
+ - ID 기반 테이블 매칭으로 정확한 XML 업데이트 구현
349
+ - 원본 XML 구조를 최대한 보존하면서 텍스트만 수정
350
+
351
+ ### 0.1.0
352
+ - 최초 릴리스
353
+
354
+ ## 라이선스
355
+
356
+ MIT
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Hanging Indent Calculator (내어쓰기 자동 계산기)
3
+ *
4
+ * 마커 기반 룩업 테이블 + 폰트 크기 스케일링으로
5
+ * 80% 이상의 정확성을 목표로 함
6
+ *
7
+ * 개선 v2:
8
+ * - 앞 공백 포함 계산
9
+ * - 한글 폰트 보정 계수 적용
10
+ * - 공백 너비 조정
11
+ */
12
+ export interface MarkerInfo {
13
+ marker: string;
14
+ type: MarkerType;
15
+ leadingSpaces: number;
16
+ }
17
+ export type MarkerType = 'bullet' | 'number' | 'korean' | 'parenthesized' | 'parenthesized_korean' | 'circled' | 'roman' | 'alpha' | 'article';
18
+ export declare class HangingIndentCalculator {
19
+ private static readonly DEFAULT_FONT_SIZE;
20
+ /**
21
+ * 문자의 너비를 em 단위로 반환
22
+ */
23
+ private getCharWidth;
24
+ /**
25
+ * 마커 문자열의 너비를 em 단위로 계산
26
+ */
27
+ private calculateMarkerWidthInEm;
28
+ /**
29
+ * 마커 너비 계산 (points 단위)
30
+ *
31
+ * @param marker 마커 문자열 (예: "○ ", "1. ")
32
+ * @param fontSize 폰트 크기 (pt)
33
+ * @param fontName 폰트 이름 (선택적, 기본값 사용시 생략)
34
+ * @returns 마커의 너비 (pt)
35
+ */
36
+ calculateMarkerWidth(marker: string, fontSize: number, fontName?: string | null): number;
37
+ /**
38
+ * 텍스트에서 마커 감지 (앞 공백 포함)
39
+ *
40
+ * @param text 텍스트
41
+ * @returns 마커 정보 또는 null
42
+ */
43
+ detectMarker(text: string): MarkerInfo | null;
44
+ /**
45
+ * 텍스트에서 내어쓰기 값 자동 계산 (points 단위)
46
+ *
47
+ * @param text 텍스트
48
+ * @param fontSize 폰트 크기 (pt, 기본값 12pt)
49
+ * @param fontName 폰트 이름 (선택적, 기본값 사용시 생략)
50
+ * @returns 내어쓰기 값 (pt)
51
+ */
52
+ calculateHangingIndent(text: string, fontSize?: number, fontName?: string | null): number;
53
+ /**
54
+ * points를 HWPUNIT으로 변환
55
+ *
56
+ * @param points 포인트 값
57
+ * @returns HWPUNIT 값 (points × 100)
58
+ */
59
+ toHwpUnit(points: number): number;
60
+ /**
61
+ * 텍스트에서 내어쓰기 값 자동 계산 (HWPUNIT 단위)
62
+ *
63
+ * @param text 텍스트
64
+ * @param fontSize 폰트 크기 (pt, 기본값 12pt)
65
+ * @param fontName 폰트 이름 (선택적, 기본값 사용시 생략)
66
+ * @returns 내어쓰기 값 (HWPUNIT)
67
+ */
68
+ calculateHangingIndentInHwpUnit(text: string, fontSize?: number, fontName?: string | null): number;
69
+ }