Skip to content

Repository files navigation

mcp-document

An MCP server that parses office documents and writes them.

Tool Takes Returns
read_document(content, filename?) DOCX, PPTX, XLSX, HWP, HWPX, ODT/ODS/ODP, RTF — bytes as base64 the text, as Markdown that keeps the document's shape
inspect_document(content, filename?, from?, to?) the same formats except XLSX the structure, one line per block — spans, alignment, levels, pictures
inspect_spreadsheet(content, filename?, mode?, includeHidden?) XLSX bytes as base64 addressed cached values and formulas, without executing them
render_spreadsheet(sheets, title?, filename?) named rows and explicit formula cells a new XLSX file, as an MCP resource block
render_document(format, content, profile?, title?, filename?, assets?) Markdown → docx pptx pdf hwpx the file, as an MCP resource block

A document is not a list of its words. A report's table says which column a figure belongs to, a contract's numbering says which clause is which, and a deck says what was a title. Reading all of that out as one line after another loses the part a reader was going to use, so read_document returns Markdown that keeps it — real tables with their alignment, lists that count, headings at their level, links with their targets, a mark where a picture stood. inspect_document carries what Markdown has no syntax for: which cells a merge covers, which row the document itself called a header, where a shape sat on its slide.

It exists because a document is not its text, and a report is not a file. An agent handed a .hwp or a .docx cannot open it — the format is a container it has no way through. And an agent that has written a report has nothing to hand to a person: text in a chat window is not a document somebody can file, print or send on. This closes both gaps, and only those.

What it deliberately no longer does: fetch, store, or read a PDF. All three left in the same change, and for one reason — each was a second copy of something the caller already had. The outbound boundary here (an SSRF guard, a pinned-DNS fetch, a redirect policy) was byte-for-byte the sibling's. The PDF reader was byte-for-byte the caller's, around the same unpdf. And the S3 upload meant a second bucket, a second retention policy and a second AWS credential for bytes the caller was already storing everything else in.

So this is the parser and renderer, and only those: the formats that genuinely need one. A caller sending a PDF, a web page or a text file gets a refusal that names the format and says who reads it — silence would read as "this document is unreadable", which is a different and much more damaging claim.

The documentation

Architecture Why the protocol uses the SDK, and why a rendered file comes back as bytes
Reading What the readers take, how a format is decided, and what extraction omits
Writing The Markdown render_document understands, and the images it embeds
Spreadsheets Formula-safe XLSX inspection and new-workbook creation
The design system The palette, the type scale and the language every renderer reads from theme.ts
The document engine How docx, pdf and hwpx become a report — and the Korean font inside the PDF
The presentation engine How pptx becomes a planned deck
Safety The threat model, the limits, and the two authentication modes
Operations Configuration, the endpoints, the image, and registering it with a client
Development The commands, the release, and what the tests do and do not cover

Run

npm install          # Node >= 24
npm run dev          # tsx, no build step
npm test             # node --test, no test framework

MCP_API_KEY=<secret> node dist/server.js   # after npm run build

PORT (default 3000) and MCP_API_KEY are the only two settings, and with the key unset the server answers anyone that can reach it — Safety says what that mode assumes. Everything else about deploying it is in Operations.

About

No description or website provided.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages