npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rhwp-mcp-server

v0.1.0-beta.1

Published

MCP server for Korean HWP/HWPX documents — read, fill form fields, author new docs, preview — powered by @rhwp/core (Rust + WebAssembly).

Readme

rhwp-mcp-server

MCP server for Korean HWP / HWPX documents — read, fill form fields, fill cells, author new docs, base64 round-trip — powered by @rhwp/core (Rust + WebAssembly).

Status: 0.1.0-beta.1 — private-beta release prep. 15 tools wired to real @rhwp/core calls, 66 vitest cases passing, binary-identity gate 5/5 PASS on the synthetic baseline. See CHANGELOG.md for the full Sprint 1 → 3 trail.


What this is

An MCP server that lets an LLM (Claude Desktop, Claude Code, Cursor, Claude Web/Mobile via base64) work with Korean .hwp and .hwpx files natively. The design balances three personas equally — none is the headline:

  1. 공공기관·HR·총무 자동화 (Form Filler) — bulk-fill 한컴 양식 (이력서 / 공문 / 계약서 / 가정통신문) from structured data. 누름틀 and table-cell layouts both supported.
  2. 지식 노동자 / 개발자 (Document Editor) — author new HWP / HWPX documents top-down: title, body, tables, styled char shape, paragraph layout. No 한컴오피스 license required.
  3. 호환성 민감 사용자 (Hancom Bridge) — read/write HWP ↔ HWPX safely on machines without 한컴오피스 installed. Includes a binary-identity save gate (ADR-0002) so round-trips are auditable.

Quick start

npm install -g rhwp-mcp-server@beta   # or use npx in your client config

Claude Desktop config (claude_desktop_config.json):

{
  "mcpServers": {
    "rhwp": {
      "command": "npx",
      "args": ["-y", "rhwp-mcp-server@beta"]
    }
  }
}

Other clients: see docs/setup/claude-desktop.md, docs/setup/cursor.md, and docs/setup/claude-code.md.

Once configured, try:

  • "이력서 양식 ~/Documents/resume.hwp에 이 정보로 채워줘."hwp_openhwp_list_fields / hwp_locate_blankshwp_fill_fields / hwp_fill_cellshwp_save_as.
  • "가정통신문 초안 만들어줘. 빈 문서에서 시작."hwp_open_blankhwp_insert_text (with style) → hwp_create_tablehwp_save_as.
  • "이 base64 .hwp를 .hwpx로 변환해줘."hwp_open_base64_validatedhwp_save_as_base64 (format: hwpx).

End-to-end walkthroughs per persona live under docs/persona-examples/:

  • form-automation.md — 35-cell 이력서 fill on a real school form.
  • authoring.md — 가정통신문 with title style, body paragraphs, attendee table.
  • compat.md — base64 wire transit + binary-identity gate.

Tools — 15 + hwp_ping

Form filling (5)

| Tool | Purpose | | --- | --- | | hwp_list_fields | Enumerate 누름틀 form-field controls (name, type, current value). | | hwp_fill_fields(map) | Bulk-fill 누름틀 by name. Unknown names → skipped. | | hwp_locate_blanks | Enumerate empty table cells with a suggested label (left-neighbor → header-row). | | hwp_fill_cells(map) | Fill table cells by 'row,col' or by inferred label. Unresolvable keys → typed skipped reason. | | hwp_apply_action(name='setFieldValueByName', …) | Escape hatch — call any rhwp field API by name. |

Authoring (4)

| Tool | Purpose | | --- | --- | | hwp_insert_text(text, style?) | Insert text at document start with optional char-level style (font_size pt, bold, italic, underline, color, font_family). See ADR-0005. | | hwp_create_table(rows, cols, data?) | Insert a table, optionally pre-filled cell-by-cell. | | hwp_set_paragraph_style(style) | Apply paragraph-level layout (alignment, indent, line spacing). | | hwp_apply_action(name, params) | Generic dispatcher — call any rhwp action with explicit coordinates (35 catalog entries across text / table / paragraph / header_footer / page / field / image / math / other). |

Document I/O (5)

| Tool | Purpose | | --- | --- | | hwp_open(path) | Open .hwp or .hwpx by filesystem path. | | hwp_save_as(path, format?) | Save current doc by path. Format default hwpx. | | hwp_open_blank() | Bootstrap a blank doc without touching the filesystem. | | hwp_open_base64(bytes_base64, format?) | Load a doc from a base64 byte string (no filesystem needed). | | hwp_save_as_base64(format) | Serialize current doc to a base64 byte string + length. |

Hardened I/O (1)

| Tool | Purpose | | --- | --- | | hwp_open_base64_validated(bytes_base64, expected_bytes?, expected_crc32?) | Same as hwp_open_base64 with explicit length + CRC32 integrity checks. Wire-corruption surfaces as a typed parse/BAD_LENGTH or parse/BAD_CHECKSUM, not a WASM panic. |

Catalog (2)

| Tool | Purpose | | --- | --- | | hwp_apply_action(name, params) | Generic dispatcher — invoke any rhwp action by name (insertText, createTable, applyParaFormat, applyCharFormat, replaceAll, insertEquation, insertPicture, createHeaderFooter, setPageDef, …). | | hwp_list_actions(category?) | Discover available actions with JSON Schemas. Categories: text / table / paragraph / header_footer / page / field / image / math / other. |

The 15 + hwp_ping smoke tool stay under the 8 K token budget for tool descriptions, measured by npm run measure:tokens.

Errors are typed

Every rhwp WASM call flows through wrapPanic and surfaces as a typed RhwpError with category and code. Examples:

  • parse/BAD_BASE64 — base64 input was not valid base64.
  • parse/BAD_LENGTHexpected_bytes mismatch (corruption detected).
  • parse/BAD_CHECKSUM — CRC32 mismatch (corruption detected).
  • field/FILL_FAILED — rhwp setFieldValueByName returned ok:false.
  • action/APPLY_CHAR_FORMAT_FAILED — rhwp applyCharFormat returned ok:false.
  • */WASM_TRAP — a Rust panic crossed the WASM boundary; the original error is preserved in cause for diagnosis.

No raw RuntimeError: unreachable executed reaches the MCP client.

Architecture (one paragraph)

A Node.js stdio MCP server holds a single global rhwp document in memory (SessionStore). hwp_open / hwp_open_blank / hwp_open_base64 / hwp_open_base64_validated load, subsequent tools mutate, hwp_save_as / hwp_save_as_base64 flush. WASM warms on server start (~30-70 ms after first load) so the first tool call never pays instantiation cost. Rust panics from @rhwp/core are caught at the WASM boundary by wrapPanic and surfaced as classified RhwpErrors — no opaque unreachable executed reaches the MCP client. See docs/architecture.md for the full picture and docs/decisions/ for the 6 accepted ADRs.

Verification

  • vitest: npm test — 17 files / 66 tests pass (smoke per tool + Sprint 3 corpus-pass logic + Sprint 1.5 binary-identity probe).
  • schema:diff: npm run schema:diff — per-PR drift guard against the locked tool surface; current snapshot is 15 tools clean.
  • gate:binary-identity: npm run gate:binary-identity — Sprint 1.5 / Sprint 3 combined corpus gate (Pass A field round-trip + Pass B binary identity). Current synthetic baseline = 5/5 PASS at Sprint 1.5 90% threshold (auto-escalates to Decision Gate 3.0 ≥ 95% when rated corpus ≥ 30). See ADR-0006.
  • probes: npm run probe:fields, npm run probe:char-format — runtime signature confirmations against the pinned @rhwp/core version, with markdown reports under docs/measurements/.

Development

npm ci

npm run dev               # tsx-driven server on stdio
npm run build             # tsc → dist/
npm run test              # vitest
npm run test:watch        # vitest --watch

npm run probe:fields            # → docs/measurements/rhwp-field-api.md
npm run probe:char-format       # → docs/measurements/rhwp-char-format.md
npm run measure:tokens          # tool-description budget check
npm run schema:diff             # CI guard against signature drift
npm run schema:snapshot         # regenerate schemas/snapshot.json
npm run corpus:generate         # synthetic .hwp corpus → corpus/synthetic/
npm run gate:binary-identity    # run Pass A + Pass B corpus gate
npm run generate:catalog-manifest # regenerate src/rhwp/catalog-manifest.json
npm run lint:md                 # markdown lint

Releases

Private beta lives on the beta dist-tag:

npm install -g rhwp-mcp-server@beta
# or
npx rhwp-mcp-server@beta

Feedback program details: docs/release/private-beta-program.md.

Decision records

| ADR | Subject | Status | | --- | --- | --- | | 0001 | hwp_preview image renderer | Deferred to v0.2 (Sprint 3 prep) | | 0002 | Binary-identity Pass B baseline (Sprint 1.5) | Accepted | | 0003 | Base64 wire-friendly contract (Sprint 2.5) | Accepted | | 0004 | Cell-based fill peer to field-based fill (Sprint 2.6) | Accepted | | 0005 | hwp_insert_text style → applyCharFormat chain (Sprint 2.7) | Accepted | | 0006 | Decision Gate 3.0 structure (Pass A + Pass B combined, threshold escalation) | Accepted (structure); verdict pending N=30 corpus |

License

MIT — see LICENSE. Third-party attributions in NOTICE.