LLM-friendly document template editing (DOCX/PPTX/HWPX/XLSX) with MCP server and Claude API tool-use support
Project description
document-adapter
LLM이 DOCX / PPTX / HWPX 문서를 직접 편집할 수 있게 해주는 통합 어댑터 + MCP 서버.
세 가지 오피스 포맷을 하나의 파이썬 인터페이스로 추상화하고, Claude Desktop / Claude Code / Anthropic API Tool Use에서 바로 호출할 수 있는 MCP 도구로 노출합니다. 양식 문서의 빈 셀을 자동으로 채우거나, 템플릿의 {{key}}를 치환하거나, 기존 표의 내용을 수정하는 작업을 LLM 에이전트가 수행할 수 있습니다.
- 📦 PyPI: https://pypi.org/project/document-adapter/
- 🔗 Repo: https://github.com/PlateerLab/document-adapter
지원 포맷
| 포맷 | 백엔드 | 템플릿 렌더 | 표 읽기 | 병합 셀 인지 | 중첩 테이블 | 셀 수정 | 행 추가 |
|---|---|---|---|---|---|---|---|
.docx |
docxtpl + python-docx |
Jinja2 ({%tr%} loop 포함) |
✅ | ✅ | ✅ | ✅ | ✅ |
.pptx |
python-pptx + 자체 lxml 확장 |
{{key}} 치환 |
✅ (슬라이드 위치 포함) | ✅ | — (포맷 미지원) | ✅ | ✅ (v0.5+) |
.hwpx |
자체 hwpx_core (lxml + zipfile) |
{{key}} 치환 |
✅ | ✅ | ✅ | ✅ | ✅ |
.xlsx |
openpyxl |
{{key}} 치환 |
✅ (시트 = 표) | ✅ | — | ✅ | ✅ |
- HWPX는 한컴오피스 설치가 불필요합니다 (macOS/Linux 서버에서 그대로 동작).
- 구버전
.hwp(바이너리 포맷)는 지원하지 않습니다 —.hwpx로 변환 후 사용하세요. - XLSX (v0.10+): 각 워크시트를 하나의 표로 매핑(
table_index= 시트 인덱스,location= 시트명). 병합 셀·fill_form·render_template동일 인터페이스로 동작.- 셀 타입: 날짜는 날짜만 표시,
set_cell은 깔끔한 숫자(금액)를 숫자형으로 기록(전화·우편번호는 문자 유지). - 수식 셀: 캐시된 계산값이 있으면(Excel 저장본) 그 값을, 없으면(openpyxl 생성본 등) 수식 문자열(
=A1+B1)을 표시. 수식 자체는 편집/저장 시 보존됩니다.
- 셀 타입: 날짜는 날짜만 표시,
- 병합 셀: 3개 포맷 모두 preview에
null슬롯 +merges메타로 구조 노출. non-anchor 좌표에 쓰기는MergedCellWriteError로 거부. - 셀 크기 메타 (v0.6+):
get_tables는column_widths_cm/row_heights_cm,get_cell은width_cm/height_cm/char_count를 반환합니다. LLM이 좁은 셀(예: 1.7×0.7cm 배지)에 긴 텍스트를 넣어 오버플로 되는 것을 사전에 판단할 수 있습니다.
라이선스 (상용 사용 가능)
- 본 프로젝트: Apache License 2.0 — 재배포·파생물 배포 시 저작권·
NOTICE출처 표시 유지 필요(라이선스 §4). 상용 사용 가능, 특허 사용권 포함. - 런타임 의존성(
python-docx,docxtpl,python-pptx,lxml,mcp): 전부 허용형 OSS (MIT/BSD/Apache-2.0/LGPL-2.1). 상용·내부 서비스에 그대로 포함 가능. - v0.3 이하에서 사용했던
python-hwpx(Non-Commercial License) 는 v0.4.0부터 dev 환경(테스트 fixture 생성) 전용으로 이동. HWPX 편집은 자체hwpx_core모듈이 수행합니다.
설치
pip install document-adapter
Claude API 예시 스크립트까지 포함:
pip install "document-adapter[claude]"
개발 환경에서 소스로 설치:
git clone https://github.com/PlateerLab/document-adapter.git
cd document-adapter
pip install -e ".[dev]"
Python 3.10+ 필요.
빠른 시작 — 파이썬 API
from document_adapter import load
doc = load("report_template.docx")
# 1. 구조 파악
schema = doc.get_schema()
print(schema.placeholders) # ['author', 'date', 'title']
print(schema.tables) # [TableSchema(index=0, rows=7, cols=2, ...), ...]
# 2. 템플릿 렌더
doc.render_template({
"title": "Q1 운영 리포트",
"author": "손성준",
"date": "2026-04-15",
})
doc.save("report_filled.docx")
# 3. 기존 양식 파일의 표 셀 수정
doc = load("checklist.docx")
# 빈 셀 값 교체
old = doc.set_cell(table_index=1, row=1, col=1, value="○○전자")
# 라벨이 있는 셀 ("성 명")에 값 추가 → "성 명 홍길동"
doc.append_to_cell(table_index=2, row=0, col=0, value="홍길동")
# 셀 전체 텍스트 + 병합 메타 조회 (preview의 40자 잘림 없이)
cell = doc.get_cell(table_index=1, row=3, col=2)
print(cell.text, cell.is_anchor, cell.span, cell.nested_table_indices)
# DOCX/PPTX/HWPX 전부 행 추가 지원 (v0.5+)
doc.append_row(1, ["새 항목", "값"])
doc.save("checklist_filled.docx")
doc.close()
라벨 기반 일괄 채우기 (v0.7+)
LLM 이 좌표 (table_index, row, col) 를 직접 계산하지 않고 "접수번호", "성명" 같은 사람이 읽는 라벨 key-value 로 양식을 채울 수 있습니다.
doc = load("form.hwpx")
result = doc.fill_form({
"접수번호": "2026-0001",
"성 명": "홍길동",
"주 소": "서울시 강남구",
"금융회사": "국민은행",
})
# → {"filled": [...], "not_found": [...], "ambiguous": [...]}
doc.save()
doc.close()
auto(기본): 라벨 셀 오른쪽 → 아래 → 같은 셀 순으로 값 셀 탐색. 보수적이라 기존 값 있는 셀은 다른 라벨로 간주하고 skip.direction="right"명시: 라벨 오른쪽 셀을 덮어쓰기 (예시값 있는 PPTX 템플릿 등).- Dot-path 섹션 지정: 동일 라벨이 여러 섹션에 있으면
"피해자.금액","지급정지요청계좌.금액"처럼 섹션 힌트 부여.ambiguous반환 시hint필드에 예시 제공. - 팁: 한 양식의 관련 라벨을 한 번에 dict 로 넘기면 라벨끼리 서로 보호되어 오염을 방지합니다.
셀 크기 메타 (v0.6+)
get_tables()가 column_widths_cm / row_heights_cm 를, get_cell()이 width_cm / height_cm / char_count 를 반환해 LLM이 좁은 셀에 긴 텍스트를 넣어 오버플로 되는 것을 사전에 판단할 수 있습니다.
cell = doc.get_cell(table_index=0, row=0, col=0)
print(cell.width_cm, cell.char_count) # 1.7cm, 4자 — 작은 배지
DOCX/PPTX는 EMU → cm, HWPX는 HU → cm 자동 환산 (1자리 반올림).
확장자로 자동 분기되므로 .pptx / .hwpx도 동일한 API를 사용합니다.
병합 셀 인지 동작 (v0.2+)
schema = doc.get_schema()
t = schema.tables[0]
# preview는 logical grid. 병합된 non-anchor 슬롯은 None.
# [['HEADER', None, None], ['A1', 'A2', 'A3']]
print(t.preview)
# merges는 span>1x1인 anchor 목록
# [MergeInfo(anchor=(0,0), span=(1,3))]
print(t.merges)
# non-anchor에 쓰기 시도하면 MergedCellWriteError (ValueError 서브클래스)
try:
doc.set_cell(0, 0, 2, "X")
except ValueError as e:
print(e) # "cell (0,2) is part of a merged region anchored at (0,0)..."
# 의도적으로 앵커로 리디렉트하고 싶다면
doc.set_cell(0, 0, 2, "X", allow_merge_redirect=True) # 경고 + 실제로 (0,0) 수정
MCP 서버로 사용 — Claude Desktop / Claude Code
실행
python -m document_adapter.mcp_server
# 또는 설치 후
document-adapter-mcp
Claude Desktop 설정
~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"document-adapter": {
"command": "/absolute/path/to/venv/bin/python",
"args": ["-m", "document_adapter.mcp_server"]
}
}
}
재시작하면 Claude Desktop에서 아래 7개 도구를 사용할 수 있습니다.
Claude Code 설정
claude mcp add document-adapter \
/absolute/path/to/venv/bin/python -m document_adapter.mcp_server
Anthropic API Tool Use로 사용
document_adapter.tools가 Claude API의 tool schema 형식과 그대로 호환됩니다.
import anthropic
from document_adapter.tools import TOOL_DEFINITIONS, call_tool
client = anthropic.Anthropic()
resp = client.messages.create(
model="claude-opus-4-6",
max_tokens=4096,
tools=[{
"name": t["name"],
"description": t["description"],
"input_schema": t["input_schema"],
} for t in TOOL_DEFINITIONS],
messages=[{
"role": "user",
"content": "report_template.docx의 표 구조를 확인하고 빈 셀을 적절히 채워줘",
}],
)
# tool_use 블록을 받으면 call_tool(name, args)로 실행 후 결과 반환
전체 agent loop 예시는 examples/claude_api_example.py 참고.
노출되는 도구
총 12 개 도구 (DOCX/PPTX/HWPX/XLSX 공통, 확장자로 자동 디스패치):
| 도구 | 설명 |
|---|---|
inspect_document |
문서 구조(placeholders, tables + column_widths_cm/row_heights_cm)를 JSON으로 반환. 같은 라벨이 여러 곳이면 duplicate_labels 힌트도 포함. 항상 첫 호출로 사용 |
render_template |
{{key}} 치환 + 조건/표현식({% if %}, {{ a*b }}). on_missing(blank/leave/error)로 누락 키 처리, rendered_keys/missing_keys 반환 |
get_cell |
셀 전체 텍스트 + 병합/중첩 메타 + width_cm/height_cm/char_count 반환 |
set_cell |
특정 표의 (row, col) 셀 값 교체 (병합 anchor만) |
append_to_cell |
기존 텍스트 뒤에 값 덧붙임 (라벨 유지용, 예: "성 명" → "성 명 홍길동") |
fill_form (v0.7+) |
라벨 이름으로 일괄 채우기. 좌표 계산 없이 {"접수번호": "...", "성명": "..."} dict. dot-path 섹션 해소 + overflow_warnings |
append_row |
표 끝에 새 행 추가 (전 포맷, v0.5+) |
get_shapes / set_shape_text (v0.8, PPTX) |
표 외 shape(textbox/placeholder/도형) 텍스트 조회·편집 |
get_form_controls / set_form_control (v0.10, HWPX) |
폼 컨트롤(체크박스·라디오·에디트·콤보) 조회·설정 |
diff_documents (v0.11) |
두 문서(편집 전/후)를 셀 단위 비교 → 변경 셀 before/after + overflow_risk. 편집 후 검증용 |
inspect_document 반환 예시 (v0.2+)
{
"format": "hwpx",
"source": "/path/to/form.hwpx",
"placeholders": [],
"tables": [
{
"index": 0,
"rows": 28,
"cols": 16,
"location": null,
"parent_path": null,
"preview": [
["포상금 지급신청서", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null],
["접수번호", null, null, "", "접수일자", null, null, "", ...]
],
"merges": [
{"anchor": [0, 0], "span": [1, 16]},
{"anchor": [1, 0], "span": [1, 3]}
]
}
]
}
LLM은 이 preview를 보고 "빈 셀이 어디 있는지 / 어떤 값을 넣어야 하는지" 를 판단하여 set_cell / append_to_cell을 호출합니다. null 슬롯은 병합된 영역이며 merges의 anchor 좌표로만 쓸 수 있습니다.
템플릿 작성 규칙
DOCX — Jinja2 전체 문법 사용 가능
{{ report_title }}
작성자: {{ author }}
{% for item in items %}- {{ item.name }}: {{ item.value }}
{% endfor %}
표 행 반복은 {%tr for ... %} / {%tr endfor %}를 각각 별도 행에 두어야 합니다.
같은 행에 두 태그를 넣으면 <w:tr> 전체가 {% for %}로 교체되어 endfor가 손실됩니다.
┌─────────────────────┬─────┬─────┐
│ 항목 │ 목표 │ 실적 │ <- 헤더
├─────────────────────┼─────┼─────┤
│ {%tr for r in rows %} │ <- for 행
├─────────────────────┼─────┼─────┤
│ {{ r.name }} │ {{ r.target }} │ {{ r.actual }} │ <- 반복 본문
├─────────────────────┼─────┼─────┤
│ {%tr endfor %} │ <- endfor 행
└─────────────────────┴─────┴─────┘
PPTX / HWPX / XLSX — {{key}} + 조건/표현식 (v0.11+)
단순 {{key}} 치환에 더해 조건({% if %})·표현식({{ price * qty }})·필터({{ x|length }}) 를 지원합니다 (셀/문단 단위 jinja2 렌더 — docx 의 Jinja 와 통일). 단순 {{key}} 만 있을 때는 기존 빠른 치환 경로를 그대로 씁니다.
- 제약: 표 행 반복(
{%tr for%}같은 구조적 row 루프)은 DOCX(docxtpl)만 지원합니다. PPTX/HWPX/XLSX 의 조건/표현식은 한 셀/문단 블록 내부 범위입니다. - PPTX는 placeholder가 여러
run으로 쪼개질 수 있어, 어댑터가 paragraph 전체 텍스트를 재조립한 뒤 첫run에 다시 담는 방식으로 처리합니다 (서식 일부 손실 가능).
누락 키 처리 — 3포맷 동일 (on_missing)
render_template(context, on_missing=...) 은 context에 없는 키를 3포맷 동일하게 처리합니다:
"blank"(기본): 빈 문자열로 — 출력에{{key}}리터럴을 남기지 않음"leave":{{key}}를 그대로 둠"error": 누락 키가 있으면ValueError(문서 미변경)
반환값은 {"used": [...], "missing": [...]} 로, 어떤 키가 치환됐고 누락됐는지 알려줍니다 (fill_form 의 filled/not_found 와 대칭).
save() 의 byte 안정성 — 포맷별 차이
- HWPX: 수정한 파트만 재직렬화하고 나머지는 원본 bytes 그대로 복사 → 편집 안 한 부분은 byte-identical (최소 diff). 원본 서식/메타 보존에 유리.
- DOCX / PPTX:
python-docx/python-pptx가 저장 시 패키지 전체를 재작성 → byte-identical 보장 안 됨 (내용은 보존되나 XML 직렬화가 달라질 수 있음).
원본과의 최소 diff가 중요한 워크플로우(버전 관리·서명 등)에서는 이 차이를 고려하세요.
내장된 버그 회피 / 백엔드 선택
| 포맷 | 문제 | 어댑터의 처리 |
|---|---|---|
| HWPX | python-hwpx 가 Non-Commercial License → 상용 배포 블로커 |
v0.4.0 부터 자체 hwpx_core 모듈 (zipfile + lxml) 로 교체. 런타임에 python-hwpx 불필요. 테스트 fixture 생성에만 사용 (dev extras) |
| PPTX | python-pptx 에 공식 add_row API 없음 (issue #86, 2014년부터 open) |
v0.5.0 부터 자체 lxml 구현 (<a:tr> deepcopy 패턴) |
| PPTX | placeholder가 여러 run으로 쪼개져 단순 run.text 치환이 실패 |
paragraph 전체 재조립 |
| DOCX | docxtpl의 {%tr%}를 같은 행에 두면 파싱 에러 |
README에 배치 규칙 명시 |
프로젝트 구조
document_adapter/
├── __init__.py # load() dispatcher
├── base.py # DocumentAdapter ABC + fill_form + dataclasses
├── docx_adapter.py # DocxAdapter
├── pptx_adapter.py # PptxAdapter (append_row 자체 구현 포함)
├── hwpx_adapter.py # HwpxAdapter (hwpx_core 기반)
├── hwpx_core/ # 자체 HWPX 패키지 (v0.4+)
│ ├── constants.py
│ ├── package.py # ZIP + dirty XML 관리
│ ├── grid.py # iter_grid, table_shape
│ └── paragraph.py # run-level 편집 헬퍼
├── tools.py # 7개 MCP 도구 정의 + call_tool dispatcher
└── mcp_server.py # MCP stdio server
examples/
└── claude_api_example.py # Claude API Tool Use 에이전트 루프
라이선스
Apache License 2.0 — LICENSE / NOTICE 참조.
이 소프트웨어나 그 파생물을 재배포할 때는 저작권·라이선스 고지와 NOTICE
파일의 출처 표시를 유지해야 합니다(라이선스 §4). 단순히 의존성으로 사용만 하는
경우엔 별도 표시 의무가 없습니다(표준 OSS 공통).
Credits
런타임 의존성 (전부 허용형 OSS):
python-docx— MITdocxtpl— LGPL-2.1python-pptx— MITlxml— BSDmcp— MIT
코드 참조:
xgen-doc2chunk(Apache-2.0) — HWPX table grid 파싱 로직 차용 (NOTICE참조)
Dev 전용 (fixture 생성에만 사용):
python-hwpx— Non-Commercial License (v0.4.0 부터 런타임 의존성 제거)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file document_adapter-0.12.0.tar.gz.
File metadata
- Download URL: document_adapter-0.12.0.tar.gz
- Upload date:
- Size: 91.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c6463df4e6eea45d15fab0a7878e58964954b6a8510000b9d8bdab5f77605d34
|
|
| MD5 |
19daa0b5fc09367fd6a88beec498e441
|
|
| BLAKE2b-256 |
6d200a2db2864a247019323010dba6b8a80403e6f44da0d37ef219e1a80b6c38
|
File details
Details for the file document_adapter-0.12.0-py3-none-any.whl.
File metadata
- Download URL: document_adapter-0.12.0-py3-none-any.whl
- Upload date:
- Size: 74.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
59dcb6a94c62ba75789bf189e2270918b59e7e11aecd6621641313b58791e6f6
|
|
| MD5 |
4f658ec42e1820504d6775601649271e
|
|
| BLAKE2b-256 |
9d5a5c3668e5e6f28c2eb8b1300b79f8cee03b4fc9d725c456f3868772206e4b
|