feat: add outline_document tool projecting a document's table of contents - #198
Merged
Conversation
…ents
The server gains a tenth tool module: outline_document reads any source
document through documents.js's DocumentConverter port, takes the
tree-form DocumentPackage the conversion reports on
ConversionResult.package, and runs document-outline.js's buildOutline
over it, answering the TOC as structured JSON an MCP client renders
directly -- groups carry { text, level, children } for headings, list
items, slides, sheets, and draw pages; leaves carry { kind, text }.
The internal conversion target is chosen per source format to preserve
the source's own content kind (a projection conversion such as
docx-to-csv would outline a spreadsheet projection of a wordprocessing
document, not the document), and to avoid a PDF layout pass wherever
the matrix allows one. odf is the one format whose only conversion is
to pdf, and a pdf source's content view is itself a read of the layout
it already records, not a new layout pass.
A leaf's kind label comes from a leafKind helper rather than a kind
property read: two leaf classes in document-schema.js carry no kind
literal at all (a formula, identified structurally by its mathml
payload, and the sheet-anchored standalone ContentEmbeddedObject), so
the label falls back to the same structural discrimination idiom
document-outline.js's own outlineLeafText uses.
…he bundle
outline_document imported buildOutline/outlineLeafText from a copy of
document-outline.js placed in node_modules by hand, invisible to
package.json and pnpm-lock.yaml.
An import rolldown cannot resolve through `dependencies` is not external,
so tsdown inlined the whole of document-outline.js into dist/index.js and
dist/bin.js, and with it document-schema.js, which document-outline.js
depends on -- a second copy of the schema types in a build that already
reaches document-schema.js through documents.js as a real external
dependency, the same duplicate-install hazard from-package.ts narrows by
error.name to survive.
Declaring document-outline.js as a dependency makes both resolve external
again: the ESM builds now carry a bare
`import { ... } from "document-outline.js"` and the CJS build a matching
require, with document-schema.js reached transitively through the single
installed copy documents.js already resolves.
The bare package name in minimumReleaseAgeExclude follows the entries
already there for this family's other sibling packages, each published
only by its own semantic-release CI through npm OIDC trusted publishing.
minimumReleaseAge is unchanged, and still gates CI's frozen-lockfile
install, which ignores the exclude list.
Mearman
force-pushed
the
feat/outline-tool
branch
from
August 19, 2026 01:37
2d205a0 to
d217ff4
Compare
Mearman
marked this pull request as ready for review
August 19, 2026 01:39
documents.js's SVG writer refuses to build a multi-page document
(buildSvgText throws SvgMultiPageNotSpecifiedError past one page), so
any odg source with more than one page failed outline_document
entirely. pdf is the only remaining target in the conversion matrix
that keeps the same content kind ('drawing') without a one-page
ceiling, matching odf's existing pdf-only routing for the same reason.
Two format-specific tests (markdown, ods) plus an unreadable-docx error case exercised only 2 of the then-12 OUTLINE_PROBE_TARGETS entries -- exactly how the odg target shipped broken, since nothing in the suite ever drove a multi-page odg source through the tool. Adds a dedicated regression pinning the multi-page odg case exactly (three pages, each an empty 'Page N' group, kind 'drawing'), plus a table-driven case per remaining source format (csv, docx, odf, odp, odt, pdf, pptx, svg, xlsx) asserting the tool answers without error and reports the source's own content kind. A companion assertion checks the fixture table's keys plus the three deep-asserted formats cover every DocumentFormat DOCUMENT_FORMATS lists, so a future format addition with no fixture fails loudly instead of silently going untested. odf-formula-fixture.ts hand-authors a real .odf package byte-for-byte (mimetype-first-and-stored zip, office:body > office:math > math:math under the real "math:" namespace prefix), mirroring this repo's own odm-fixture.ts convention -- neither odf.js nor documents.js exposes an odf writer of any kind.
Contributor
|
🎉 This PR is included in version 2.1.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a tenth tool module,
outline_document: reads any source document throughdocuments.js'sDocumentConverterport, takes the tree-formDocumentPackagethe conversion reports onConversionResult.package, and runsdocument-outline.js'sbuildOutlineover it. Groups carry{ text, level, children }for headings, list items, slides, sheets, and draw pages; leaves carry{ kind, text }. The internal conversion target is chosen per source format to preserve the source's own content kind and avoid a PDF layout pass wherever the matrix allows one.Follows the existing per-tool module convention under
src/tools/, registered fromsrc/server.tsalongside the other nine tools, with unit tests and a smoke-test update.document-outline.js1.0.0 is on npm now, so this declares it as a real dependency (pnpm add document-outline.js@^1.0.0) in place of the hand-copiednode_modules/document-outline.jsthe tool was originally developed against. That copy was invisible topackage.json/pnpm-lock.yaml, and an import rolldown can't resolve throughdependenciesisn't external — so tsdown was inlining the whole ofdocument-outline.js, and through itdocument-schema.js, straight intodist/index.jsanddist/bin.js, duplicating thedocument-schema.jscopydocuments.jsalready brings in as a proper external dependency. Same duplicate-install hazardfrom-package.ts's error handling already narrows byerror.nameto survive.The build log is the proof it's actually fixed rather than just building. Before, every one of the three outputs printed:
Now that section is gone entirely, and the outputs shrink accordingly —
dist/index.js71.95 kB → 42.12 kB,dist/bin.js72.08 kB → 42.25 kB,dist/index.cjs74.51 kB → 43.24 kB.dist/index.jsanddist/bin.jscarry a bareimport { buildOutline, isOutlineNode, outlineLeafText } from "document-outline.js", anddist/index.cjsthe matchingrequire.pnpm-workspace.yamlgainsdocument-outline.jsinminimumReleaseAgeExclude, as a bare package name like the family's other sibling packages already listed there.minimumReleaseAgestays at 60 — that's the one CI's--frozen-lockfileinstall actually respects (it ignores the exclude list), so a CI run started inside the hour afterdocument-outline.js's publish fails the age check until that window elapses, and just needs rerunning afterwards.Rebased onto current
main. Typecheck, lint, unit tests, workers tests, smoke tests, and the build all pass locally.Refs ExaDev/document-schema.js#20, ExaDev/document-outline.js#2.
Generated by Claude Code