airmang/hwpx-mcp-server<p align="center"> <h1 align="center">๐ hwpx-mcp-server</h1> <p align="center"> <strong>ํ๊ธ(HWPX) ๋ฌธ์๋ฅผ AI๋ก ์๋ํํ๋ MCP ์๋ฒ</strong> </p> <p align="center"> ํ๊ธ ์๋ํ๋ก์ธ์ ์์ด ยท ์์ ํ์ด์ฌ ยท ํฌ๋ก์ค ํ๋ซํผ </p> <p align="center"> <a href="https://pypi.org/project/hwpx-mcp-server/"><img src="https://img.shields.io/pypi/v/hwpx-mcp-server?style=flat-square&color=blue" alt="PyPI"></a> <a href="https://pypi.org/project/hwpx-mcp-server/"><img src="https://img.shields.io/pypi/pyversions/hwpx-mcp-server?style=flat-square" alt="Python"></a> <a href="https://github.com/airmang/hwpx-mcp-server/blob/main/LICENSE"><img src="https://img.shields.io/github/license/airmang/hwpx-mcp-server?style=flat-square" alt="License"></a> <a href="https://github.com/airmang/hwpx-mcp-server/actions"><img src="https://img.shields.io/github/actions/workflow/status/airmang/hwpx-mcp-server/test.yml?style=flat-square&label=tests" alt="Tests"></a> </p> </p> --- **hwpx-mcp-server**๋ [Model Context Protocol(MCP)](https://modelcontextprotocol.io) ํ์ค์ ๋ฐ๋ฅด๋ ์๋ฒ๋ก, [python-hwpx](https://github.com/airmang/python-hwpx) ๊ธฐ๋ฐ์์ HWPX ๋ฌธ์์ ์ด๋ ยท ๊ฒ์ ยท ํธ์ง ยท ์ ์ฅ์ AI ํด๋ผ์ด์ธํธ์์ ์ง์ ์ํํ ์ ์๊ฒ ํฉ๋๋ค. > **Note** โ ์ด ์๋ฒ๋ Open XML ๊ธฐ๋ฐ `.hwpx` ํฌ๋งท์ ์ง์ํฉ๋๋ค. ๋ ๊ฑฐ์ ๋ฐ์ด๋๋ฆฌ `.hwp` ํฌ๋งท์ ์ง์ ํธ์ง ๋์์ด ์๋๋๋ค. <br> ## Why? ๊ตญ๋ด ๊ณต๊ณต๊ธฐ๊ดยทํ๊ตยท๊ธฐ์ ์์๋ ํ๊ธ ๋ฌธ์ ๊ธฐ๋ฐ ์ ๋ฌด๊ฐ ๋งค์ฐ ๋ง์ง๋ง, ์๋ํ๋ ์ค๋ซ๋์ OS/ํ๋ก๊ทธ๋จ ์์กด์ฑ์ด ์ปธ์ต๋๋ค. **hwpx-mcp-server**๋ ์ด ์ ์ฝ์ ์ค์ด๋ ๋ฐ ์ด์ ์ ๋ง์ถฅ๋๋ค. - โ **OS ๋ฌด๊ด** โ Windows, macOS, Linux์์ ๋์ - โ **ํ๊ธ ์๋ํ๋ก์ธ์ ๋ถํ์** โ ์์ ํ์ด์ฌ ๊ธฐ๋ฐ ์ฒ๋ฆฌ - โ **AI ๋ค์ดํฐ๋ธ** โ Claude Desktop, VS Code, Gemini CLI ๋ฑ MCP ํด๋ผ์ด์ธํธ์ ์ง์ ์ฐ๊ฒฐ - โ **Stateless ๊ธฐ๋ณธ ์ค๊ณ** โ ๋๊ตฌ ํธ์ถ๋ง๋ค `filename`์ ๋ช ์ํด ์ผ๊ด์ ์ผ๋ก ์คํ <br> ## Use Cases - ์ค์ ์ฌ์ฉ ์ฌ๋ก 9๊ฐ ๋ณด๊ธฐ: [`docs/use-cases.md`](docs/use-cases.md) - ์ข ํฉ ํ ์คํธ ๋ฆฌํฌํธ: [`tests/hwpx_mcp_report_updated.md`](tests/hwpx_mcp_report_updated.md) <br> ## Quick Start ### 1. ์ค์น & ์คํ [uv](https://docs.astral.sh/uv/getting-started/installation/) ๊ธฐ์ค: ```bash uvx hwpx-mcp-server ``` ๋๋ pip ์ค์น: ```bash pip install hwpx-mcp-server hwpx-mcp-server ``` ์๊ตฌ์ฌํญ: - `Python >= 3.10` - `python-hwpx >= 1.9` ### 2. MCP ํด๋ผ์ด์ธํธ ์ค์ <details> <summary><b>Claude Desktop</b></summary> `claude_desktop_config.json`: ```json { "mcpServers": { "hwpx": { "command": "uvx", "args": ["hwpx-mcp-server"] } } } ``` </details> <details> <summary><b>Gemini CLI</b></summary> `~/.gemini/settings.json`: ```json { "mcpServers": { "hwpx": { "command": "uvx", "args": ["hwpx-mcp-server"] } } } ``` </details> <details> <summary><b>VS Code (Copilot Chat)</b></summary> `.vscode/mcp.json`: ```json { "servers": { "hwpx": { "command": "uvx", "args": ["hwpx-mcp-server"] } } } ``` </details> <details> <summary><b>Cursor / Windsurf</b></summary> ๊ฐ ์๋ํฐ MCP ์ค์ ํ์ผ์ ๋์ผํ ๋ธ๋ก์ ์ถ๊ฐ: ```json { "mcpServers": { "hwpx": { "command": "uvx", "args": ["hwpx-mcp-server"] } } } ``` </details> ### 3. ์ ์ก ๋ชจ๋ ์ ํ (Stdio + Streamable HTTP) ๊ธฐ๋ณธ `stdio` ์ฌ์ฉ์ ๊ธฐ์กด๊ณผ ๋์ผํฉ๋๋ค. ```bash hwpx-mcp-server ``` ๋์ผํ MCP ๋๊ตฌ ์ธํธ๋ฅผ Streamable HTTP๋ก ์คํํ ์ ์์ต๋๋ค. ```bash hwpx-mcp-server --transport streamable-http --host 127.0.0.1 --port 8000 ``` ํ๊ฒฝ ๋ณ์๋ก๋ ๋์ผํ๊ฒ ์ ์ดํ ์ ์์ต๋๋ค. - `HWPX_MCP_TRANSPORT` (`stdio` ๋๋ `streamable-http`) - `HWPX_MCP_HOST` (๊ธฐ๋ณธ๊ฐ: `127.0.0.1`) - `HWPX_MCP_PORT` (๊ธฐ๋ณธ๊ฐ: `8000`) > ์ฐธ๊ณ : HTTP ์ธ์ฆ์ ํ์ฌ ๊ฐ๋ฐ ํธ์ ์ค์ฌ์ผ๋ก ๋จ์ํ๊ฒ ์ ์ง๋์ด ์์ต๋๋ค. ํ๋ก๋์ ์ฉ ์ธ์ฆ ํ ์ ์๋ฒ ์ง์ ์ ์ TODO๋ก ๋จ๊ฒจ๋์์ต๋๋ค. <br> ## Features ๊ธฐ๋ณธ ๋ชจ๋์์ 30๊ฐ ๋๊ตฌ, ๊ณ ๊ธ ๋ชจ๋(`HWPX_MCP_ADVANCED=1`)์์ ์ถ๊ฐ 10๊ฐ ๋๊ตฌ๊ฐ ํ์ฑํ๋ฉ๋๋ค. ### ๐ ์ฝ๊ธฐ & ํ์ | ๋๊ตฌ | ์ค๋ช | |---|---| | `get_document_info` | ๋ฌธ์ ๋ฉํ๋ฐ์ดํฐ/์น์ /๋ฌธ๋จ/ํ ๊ฐ์ ์กฐํ | | `get_document_text` | ๋ฌธ์ ์ ์ฒด ํ ์คํธ ์ถ์ถ (`max_chars` ์ง์) | | `get_document_outline` | ์ ๋ชฉ/๊ฐ์ ๊ตฌ์กฐ ์ถ์ถ | | `get_paragraph_text` | ํน์ ๋ฌธ๋จ ํ ์คํธ ์กฐํ | | `get_paragraphs_text` | ๋ฌธ๋จ ๋ฒ์ ์กฐํ | | `list_available_documents` | ํด๋ ๋ด `.hwpx` ํ์ผ ๋ชฉ๋ก ์กฐํ | ### ๐งพ ๋ณํ & ์ถ์ถ (์ ๋ ฅ ํ์ด๋ก๋ ๊ธฐ๋ฐ) | ๋๊ตฌ | ์ค๋ช | |---|---| | `hwpx_to_markdown` | HWPX ์ ๋ ฅ์ Markdown์ผ๋ก ๋ณํ | | `hwpx_to_html` | HWPX ์ ๋ ฅ์ HTML๋ก ๋ณํ | | `hwpx_extract_json` | HWPX ๊ตฌ์กฐ๋ฅผ JSON์ผ๋ก ์ถ์ถ | ๊ณตํต ์ ๋ ฅ ๊ท์น: - ์ ๋ ฅ ์์ค๋ `hwpx_base64` ๋๋ `url` ์ค ์ ํํ ํ๋๋ง ํ์ฉ - `url`์ `https://...`๋ง ํ์ฉ ๊ณตํต ์ต์ : - `output`: `full` ๋๋ `chunks` - `chunk_strategy`: `section` ๋๋ `paragraph` - `max_chars_per_chunk`: ์ฒญํฌ๋น ์ต๋ ๋ฌธ์ ์(๊ธฐ๋ณธ๊ฐ: `HWPX_MCP_MAX_CHARS_PER_CHUNK` ๋๋ `8000`) <details> <summary><b>๋ณํ/์ถ์ถ ์๋ต ์์</b></summary> #### `hwpx_to_markdown` ```json { "markdown": "# Title\n\nParagraph...", "chunks": ["..."], "meta": { "source_type": "base64", "section_count": 2, "paragraph_count": 10, "table_count": 1, "figure_caption_count": 1 } } ``` #### `hwpx_to_html` ```json { "html": "<!doctype html><html>...</html>", "chunks": ["<section>...</section>"], "meta": { "source_type": "url", "image_policy": "omitted" } } ``` #### `hwpx_extract_json` ```json { "doc": { "title": "Title", "toc": [{ "level": 1, "text": "Title", "paragraph_index": 0 }], "sections": [{ "index": 0, "title": "Title", "paragraphs": [] }], "tables": [], "figures": [] }, "chunks": [{ "chunk_index": 0, "strategy": "section", "section": {} }], "meta": { "source_type": "base64" } } ``` </details> ### ๐ ๊ฒ์ & ์นํ | ๋๊ตฌ | ์ค๋ช | |---|---| | `find_text` | ํค์๋ ๊ฒ์ + ์ปจํ ์คํธ ๋ฐํ | | `search_and_replace` | ๋จ์ผ ์นํ (split-run ๋ณด๊ฐ) | | `batch_replace` | ๋ค์ค ์นํ ์ผ๊ด ์คํ | ### โ๏ธ ํธ์ง | ๋๊ตฌ | ์ค๋ช | |---|---| | `add_heading` | ์ ๋ชฉ(ํค๋ฉ) ๋ฌธ๋จ ์ถ๊ฐ | | `add_paragraph` / `insert_paragraph` / `delete_paragraph` | ๋ฌธ๋จ ์ถ๊ฐ/์ฝ์ /์ญ์ | | `add_page_break` | ํ์ด์ง ๋๋๊ธฐ ์ถ๊ฐ | | `add_memo` / `remove_memo` | ๋ฉ๋ชจ ์ถ๊ฐ/์ ๊ฑฐ | | `copy_document` | ๋ฌธ์ ์์ ๋ณต์ฌ | ### ๐ ํ | ๋๊ตฌ | ์ค๋ช | |---|---| | `add_table` / `get_table_text` | ํ ์์ฑ/์กฐํ | | `set_table_cell_text` | ์ ํ ์คํธ ์์ | | `merge_table_cells` / `split_table_cell` | ์ ๋ณํฉ/๋ถํ | | `format_table` | ํ ํค๋ ๋ฑ ๊ธฐ๋ณธ ์์ ์ ์ฉ | ### ๐จ ์คํ์ผ | ๋๊ตฌ | ์ค๋ช | |---|---| | `format_text` | ํ ์คํธ ๋ฒ์ ์์ ์ ์ฉ(๊ตต๊ธฐ, ๊ธฐ์ธ์, ๋ฐ์ค, ์์ ๋ฑ) | | `create_custom_style` | ์ปค์คํ ์คํ์ผ ์์ฑ | | `list_styles` | ๋ฌธ์ ์คํ์ผ ๋ชฉ๋ก ์กฐํ | ### ๐ฌ ๊ณ ๊ธ (์ต์ ) `HWPX_MCP_ADVANCED=1`์ผ ๋ ํ์ฑํ: | ๋๊ตฌ | ์ค๋ช | |---|---| | `package_parts` | OPC ํํธ ๋ชฉ๋ก ์กฐํ | | `package_get_xml` / `package_get_text` | ํํธ XML/ํ ์คํธ ์กฐํ | | `object_find_by_tag` / `object_find_by_attr` | XML ์์ ๊ฒ์ | | `plan_edit` / `preview_edit` / `apply_edit` | ํธ์ง ๊ณํ/๋ฏธ๋ฆฌ๋ณด๊ธฐ/์ ์ฉ | | `validate_structure` / `lint_text_conventions` | ๊ตฌ์กฐ ๊ฒ์ฆ/ํ ์คํธ ๋ฆฐํธ | <br> ## Configuration | ๋ณ์ | ์ค๋ช | ๊ธฐ๋ณธ๊ฐ | |---|---|---| | `HWPX_MCP_MAX_CHARS` | ํ ์คํธ ๋ฐํ ๋๊ตฌ ๊ธฐ๋ณธ ์ต๋ ๊ธธ์ด | `10000` | | `HWPX_MCP_MAX_CHARS_PER_CHUNK` | ๋ณํ/์ถ์ถ ๋๊ตฌ์ ์ฒญํฌ ๋ถํ ๊ธฐ๋ณธ ๊ธธ์ด | `8000` | | `HWPX_MCP_AUTOBACKUP` | `1`์ด๋ฉด ์ ์ฅ ์ `.bak` ๋ฐฑ์ ์์ฑ | `1` | | `HWPX_MCP_ADVANCED` | `1`์ด๋ฉด ๊ณ ๊ธ ๋๊ตฌ ํ์ฑํ | `0` | | `HWPX_MCP_TRANSPORT` | ์๋ฒ ์ ์ก ๋ชจ๋ (`stdio`, `streamable-http`) | `stdio` | | `HWPX_MCP_HOST` | HTTP ๋ฐ์ธ๋ฉ ํธ์คํธ | `127.0.0.1` | | `HWPX_MCP_PORT` | HTTP ๋ฐ์ธ๋ฉ ํฌํธ | `8000` | | `LOG_LEVEL` | ๋ก๊ทธ ๋ ๋ฒจ | `INFO` | ํ๊ฒฝ ๋ณ์ ํฌํจ MCP ์ค์ ์์: ```json { "mcpServers": { "hwpx": { "command": "uvx", "args": ["hwpx-mcp-server"], "env": { "HWPX_MCP_MAX_CHARS": "12000", "HWPX_MCP_MAX_CHARS_PER_CHUNK": "8000", "HWPX_MCP_AUTOBACKUP": "1", "HWPX_MCP_ADVANCED": "0", "HWPX_MCP_TRANSPORT": "stdio", "HWPX_MCP_HOST": "127.0.0.1", "HWPX_MCP_PORT": "8000", "LOG_LEVEL": "INFO" } } } } ``` <br> ## Advanced <details> <summary><b>๐ฆ OPC ํํธ ์กฐํ</b></summary> ๊ณ ๊ธ ๋ชจ๋์์ ๋ฌธ์ ๋ด๋ถ ํํธ๋ฅผ ์ง์ ์กฐํํ ์ ์์ต๋๋ค. - `package_parts` - `package_get_xml` - `package_get_text` </details> <details> <summary><b>๐งญ ํธ์ง ํ์ดํ๋ผ์ธ</b></summary> ๊ณ ๊ธ ๋ชจ๋์์ `plan_edit โ preview_edit โ apply_edit` ํ๋ฆ์ผ๋ก ๋ณ๊ฒฝ ๊ณํ์ ๊ฒํ ํ๊ณ ์ ์ฉํ ์ ์์ต๋๋ค. </details> <details> <summary><b>๐งช ๊ตฌ์กฐ/๊ท์น ๊ฒ์ฌ</b></summary> ๊ณ ๊ธ ๋ชจ๋์์ ๋ค์ ๊ฒ์ฌ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. - `validate_structure` - `lint_text_conventions` </details> <br> ## Testing ```bash # ํ ์คํธ ์์กด์ฑ ์ค์น python -m pip install -e ".[test]" # ์ ์ฒด ํ ์คํธ python -m pytest -q ``` ๋ก์ปฌ ๊ธฐ์ค(2026-02-22) ์ ์ฒด ํ ์คํธ๊ฐ ํต๊ณผํ์ต๋๋ค. - ์ค์ ์ฌ์ฉ ์ฌ๋ก: `docs/use-cases.md` - ์ข ํฉ ๋ฆฌํฌํธ: `tests/hwpx_mcp_report_updated.md` - ํ๊ท ํ ์คํธ: `tests/test_hwpx_report_regressions.py` <br> ## Architecture ```text hwpx-mcp-server โโโ src/hwpx_mcp_server/ โ โโโ server.py # Stateless MCP ์ง์ ์ โ โโโ hwpx_ops.py # ๊ณ ๊ธ/๋ด๋ถ ์ฐ์ฐ ๋ํผ โ โโโ core/ # ๋ฌธ๋จ/ํ/๊ฒ์/์์ ํต์ฌ ๋ก์ง โ โโโ tools.py # ํ์ฅ ๋๊ตฌ ์คํค๋ง/์ ์ โ โโโ schema/ # JSON ์คํค๋ง ๋น๋/์ ๋ฆฌ๊ธฐ โโโ tests/ # ๋จ์ + E2E + ํ๊ท ํ ์คํธ โโโ pyproject.toml ``` <br> ## Comparison | | hwpx-mcp-server | hwp(๋ฐ์ด๋๋ฆฌ) COM ์๋ํ ๊ณ์ด | |---|---|---| | ๋์ ํฌ๋งท | `.hwpx` (Open XML) | `.hwp` (๋ฐ์ด๋๋ฆฌ) ์ค์ฌ | | OS | Windows ยท macOS ยท Linux | ๋์ฒด๋ก Windows ์ค์ฌ | | ํ๊ธ ํ๋ก๊ทธ๋จ ์์กด | ๋ถํ์ | ํ์ํ ๊ฒฝ์ฐ๊ฐ ๋ง์ | | ์ฐ๋ ๋ฐฉ์ | MCP + ํ์ด์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ | ๋ฐ์คํฌํฑ ์ฑ ์๋ํ | <br> ## Contributing ๊ธฐ์ฌ๋ฅผ ํ์ํฉ๋๋ค. 1. Fork ํ ๋ธ๋์น ์์ฑ 2. ๋ณ๊ฒฝ + ํ ์คํธ ์ถ๊ฐ/์์ 3. `pytest -q` ํต๊ณผ ํ์ธ ํ PR <br> ## License [MIT](LICENSE) ยฉ ๊ณ ๊ทํ (Kyuhyun Koh) <br> ## Author **๊ณ ๊ทํ** โ ๊ด๊ต๊ณ ๋ฑํ๊ต ์ ๋ณดยท์ปดํจํฐ ๊ต์ฌ - โ๏ธ [[email protected]](mailto:[email protected]) - ๐ [@airmang](https://github.com/airmang)