Skip to main content
Glama

Kookerella.FsWordDsl

A typesafe F# DSL for building Word documents, interpreted into calls against the DocumentFormat.OpenXml SDK. The DSL is a plain data model (records/DUs with structural equality) - the interpreter (Writer) compiles it to OOXML, and the reverse transform (Reader) parses an existing .docx back into the same DSL.

This is the WordprocessingML sibling of Kookerella.FsOpenXmlDsl (the Excel/SpreadsheetML one) - same objectives, same round-trip philosophy, translated to Word's own document model. See MAPPING.md for exactly which WordprocessingML features map 1:1, which are approximated, and which aren't modeled yet.

This round-trips in both directions, which most Word libraries don't: they give you an imperative API to build a document from scratch, but no way to turn an existing file back into readable source. Here, Reader parses a real .docx/.docm back into the same DSL, and Document.generateScript goes one step further and renders that model back out as a self-contained script that rebuilds an equivalent file - a decompiler for Word documents, not just a writer. Two more surfaces, Xml.toDocument/Xml.ofDocument (see "## XML" below) and Json.toDocument/Json.ofDocument (see "## JSON" below), do the same translation to/from plain XML or JSON against a real schema - for a caller who'd rather generate or consume data than write code at all.

A fluent C# wrapper (Kookerella.CsWordDsl) sits on top of the F# core for callers who'd rather not touch F# discriminated unions/option types directly - immutable records with With* builders, plus its own CsCodeGen decompiler that renders a Document back out as runnable C# source. See "## The C# wrapper" below.

An MCP server (Kookerella.FsWordDsl.Mcp) exposes the same read/write/decompile capabilities as tools any MCP-compatible AI agent can call directly, and doubles as a plain CLI (fsworddsl-mcp convert/build) for anyone not going through an MCP client at all - see its own README for the full tool list.

Layout

  • src/Kookerella.FsWordDsl - the library.

    • Units.fs - conversions between points/inches/pixels and the physical units WordprocessingML uses on the wire (twips for page geometry/spacing, EMU for image sizing).

    • Styles.fs - character and paragraph formatting: Color (Rgb, Auto, or a theme-relative Theme color - see ThemeColorKind), HighlightColor (Word's own fixed highlight palette), UnderlineStyle, RunStyle (including small caps/all caps/hidden text), ParagraphAlignment, Indentation, LineSpacingRule, TabStopAlignment/TabLeader/TabStop, ParagraphFormat (including paragraph borders, shading, and custom tab stops), BorderLineStyle, BorderSide, BorderStyle (reused for both paragraph and table borders).

    • NamedStyles.fs - StyleDefinition (paragraph or character, with BasedOn inheritance) and a small BuiltInStyles catalog (normal, heading1/2/3, title, listParagraph, hyperlinkCharStyle).

    • Numbering.fs - NumberFormatKind, ListLevel, NumberingDefinition for numbered/bulleted lists, including multi-level ones (ListLevel isn't limited to one per definition - see Builders.multiLevelNumberedListDef).

    • Hyperlinks.fs - HyperlinkTarget (external URL vs. internal bookmark reference).

    • Protection.fs - EditRestriction and DocumentProtection, document-level (Word has no per-section equivalent of Excel's per-sheet protection).

    • Revisions.fs - RevisionKind/Revision for track changes (Inline.TrackedChange, Paragraph.MarkRevision) - narrowly scoped to inserted/deleted content and paragraph marks, see MAPPING.md for what isn't covered.

    • ContentControls.fs - ContentControlType/ContentControlProps for content controls (structured document tags, w:sdt): plain text, rich text, dropdown/combo box, date picker, checkbox - see MAPPING.md for what isn't covered.

    • PageSetup.fs - PageOrientation, PageSize, PageMargins, SectionBreakType, NoteNumberRestart/NoteNumberingSettings (a section's own footnote/endnote numbering).

    • Tables.fs - TableBorders, VerticalMergeKind, TableCellProps (including a per-cell Margins override), TableStyleRef, TableStyleRegion/ TableStyleDefinition (custom table style definitions - all thirteen of OOXML's conditional-formatting regions), and CellMargins (shared shape for a table's default margins and a single cell's own override).

    • Images.fs - ImageFormat, ImageEntry (raw file bytes plus an on-page size), anchored inline within a run.

    • DocumentProperties.fs - DocumentProperties (Title, Author, Subject, Keywords, Comments, Category, Company) - core document metadata, Document.Properties.

    • Model.fs - the recursive content model: Inline (runs, breaks, images, hyperlinks, bookmarks and comments - both the single-paragraph Bookmark/Comment cases and the cross-paragraph BookmarkRangeStart/End/CommentRangeStart/End markers, simple fields, footnotes/endnotes, TrackedChange for track changes, and InlineContentControl for content controls), Paragraph (including MarkRevision), Block (paragraph, table, or ContentControlBlock - the block-level counterpart to InlineContentControl), TableCell/ TableRow (including RepeatAsHeader)/TableEntry (including CellMargins), HeaderFooterSet, SectionProperties (including BreakType and FootnoteNumbering/ EndnoteNumbering), Section, Document (including Document.VbaProject, a macro-enabled document's raw vbaProject.bin bytes, Document.Properties, and Document.TableStyles).

    • Xml.fs / Xml.xsd - the XML surface: Xml.toDocument/Xml.ofDocument translate a Document to/from an XElement tree, and Xml.schemaSet() loads the paired schema (embedded in the assembly as a resource) for validating either direction. See "## XML" below.

    • Json.fs / Json.schema.json - the JSON surface: Json.toDocument/Json.ofDocument translate a Document to/from a System.Text.Json.Nodes.JsonObject tree. Schema validation is test-suite only, not a public API. See "## JSON" below.

    • Builders.fs - plain functional constructors (section, document, withStyles, withNumbering, withProtection, withVbaProject, withDocumentProperties, withTableStyles, bulletListDef, numberedListDef, multiLevelNumberedListDef) plus DocumentDsl - smart constructors (run, para (with markRevision), hyperlink, bookmark, comment, inserted/deleted (track changes), contentControl/contentControlBlock (content controls), image, footnote, endnote, tableCell, tableRow (with height/repeatAsHeader), table (with style/borders/cellMargins)) with real optional parameters, the Word analog of the Excel repo's SheetDsl.

    • Interpreter/StyleRegistry.fs - shared run/paragraph/border/color conversions plus Document.Styles <-> styles.xml (internal).

    • Interpreter/ImageWriter.fs / ImageReader.fs - an inline image's own DSL <-> DrawingML translation (internal).

    • Interpreter/Writer.fs - DSL -> OOXML (internal).

    • Interpreter/Reader.fs - OOXML -> DSL, the reverse transform (internal).

    • Interpreter/CodeGen.fs - DSL -> F# source text: renders a Document back out as a self-contained .fsx script that rebuilds an equivalent file when run (internal).

    • Api.fs - the public Document.save/saveToStream/load/loadFromStream/ generateScript entry points.

  • src/Kookerella.CsWordDsl - the fluent C# wrapper (see "## The C# wrapper" below): immutable records/sealed record closed hierarchies mirroring the F# core's own types one-for-one, DocumentConverter.cs (internal, the two-way F#<->C# translation), DocumentIO.cs (Save/Load, the one place this project does I/O), CsCodeGen.cs (DSL -> C# source text, the C# analog of Interpreter/CodeGen.fs).

  • src/Kookerella.FsWordDsl.Mcp - the MCP server (see its own README): DocumentTools.fs (the tool surface, one [<McpServerTool>]-tagged member per tool), Program.fs (dispatches to the MCP stdio server, or to a plain convert/build CLI, depending on argv). Distributed as a dotnet tool (fsworddsl-mcp), same as the Excel sibling's own Kookerella.FsOpenXmlDsl.Mcp.

  • tests/Kookerella.FsWordDsl.Tests - one test per feature, each validating the produced file against the OOXML schema (DocumentFormat.OpenXml.Validation.OpenXmlValidator) and asserting an exact round trip back through the DSL. Each test also writes the document it builds to Examples/<test name>/output.docx (checked into the repo), plus script.fsx (regenerates the file - a separate, slower Category=Slow test group actually executes each one via dotnet fsi), document.xml, and document.json - one folder always has four views of the same example.

  • tests/Kookerella.CsWordDsl.Tests - DriftGuardTests.cs (a reflection-based tripwire checking the C# wrapper's DU mirrors haven't fallen behind the F# core's own case counts), DocumentTests.cs (targeted round-trip assertions per feature), ExampleTests.cs (reloads the F# suite's own checked-in Examples/*/output.docx fixtures rather than re-authoring every scenario a second time), CsCodeGenTests.cs (actually executes a generated file via dotnet run --file, the C# analog of the F# suite's Category=Slow dotnet fsi group).

  • samples/Kookerella.FsWordDsl.Sample - a small console app that builds a document, saves it, and reads it back.

Related MCP server: Word Document MCP Server

Quick start

open Kookerella.FsWordDsl
open type Kookerella.FsWordDsl.DocumentDsl

let doc =
    document
        [ section
              [ para ([ run "Quarterly Report" ], styleId = "Title")
                para
                    [ run "This report covers "
                      run ("Q1 2026", style = { RunStyle.Default with Bold = true })
                      run ", see the "
                      hyperlink ("full dataset", ExternalUrl "https://example.com/data")
                      run " for details." ] ] ]

doc |> Document.save "report.docx"

// Reverse transform:
let roundTripped = Document.load "report.docx"

document defaults Styles to BuiltInStyles.all, so styleId = "Heading1" (or any other built-in id) just works without registering it first - pipe withStyles afterward to replace or extend that set. run/para/hyperlink/bookmark/comment/image/ tableCell/tableRow/table are DocumentDsl members with real optional parameters (open type Kookerella.FsWordDsl.DocumentDsl brings them into scope unqualified, same as open type SheetDsl does in the Excel repo) - plain F# let bindings can't have optional parameters, which is why this part of the DSL is a type.

A Paragraph's Inlines are naturally several independently-styled runs - rich text (mixed formatting within one paragraph) is first-class, not a documented gap the way Excel's single-uniform-run Text cell is:

para
    [ run "Plain text, "
      run ("bold", style = { RunStyle.Default with Bold = true })
      run ", and "
      run ("colored", style = { RunStyle.Default with Color = Some Color.red }) ]

RunStyle also covers small caps, all caps, and hidden text; ParagraphFormat covers borders (BorderStyle, the same shape used for table borders) and shading:

para
    ([ run "ALL CAPS AND SMALL CAPS" ], format =
        { ParagraphFormat.Default with
            Borders = Some { BorderStyle.None with Bottom = Some { Style = SingleLine; Width = Some 1.0; Color = Some Color.black } }
            Shading = Some(Rgb(0xD9uy, 0xD9uy, 0xD9uy)) })

Custom tab stops (TabStop) sit on ParagraphFormat.TabStops - a right-aligned stop with a dot leader is the classic table-of-contents pattern:

para
    ([ run "Introduction"; Tab; run "1" ], format =
        { ParagraphFormat.Default with TabStops = [ { Position = 288.0; Alignment = RightTab; Leader = DotLeader } ] })

Color also accepts a theme-relative token (Theme) alongside plain Rgb/Auto - since this DSL has no theme part to resolve it against, real Word does that; Fallback is what a themeless reader sees instead, the same "always also write a computed value" convention Word itself follows:

run ("Accent-colored text", style = { RunStyle.Default with Color = Some(Theme(Accent1Theme, (0x1Fuy, 0x49uy, 0x7Duy), None, None)) })

Lists use a (numId, level) reference on the paragraph, resolved against a NumberingDefinition attached to the document - NumberingDefinition.Levels isn't limited to one level, and multiLevelNumberedListDef builds the common correctly-linked outline shape for you:

document
    [ section
          [ para ([ run "First bullet" ], numbering = (1, 0))
            para ([ run "Second bullet" ], numbering = (1, 0)) ] ]
|> withNumbering [ bulletListDef 1 ]

document
    [ section
          [ para ([ run "First topic" ], numbering = (1, 0))
            para ([ run "First subtopic" ], numbering = (1, 1))
            para ([ run "Second topic" ], numbering = (1, 0)) ] ]
|> withNumbering [ multiLevelNumberedListDef 1 3 ]

Tables are built from tableRow/tableCell, with column widths given once for the whole table - a cell without an explicit width falls back to its column's width at write time:

table (
    [ tableRow [ tableCell [ para [ run "Item" ] ]; tableCell [ para [ run "Qty" ] ] ]
      tableRow [ tableCell [ para [ run "Widgets" ] ]; tableCell [ para [ run "12" ] ] ] ],
    [ 200.0; 100.0 ],
    style = TableStyleRef.Default
)

Cell merging - horizontal (GridSpan) and vertical (RestartMerge/ContinueMerge) - are independent and combine on the same cell, matching real Word:

tableCell ([ para [ run "Spans 2 columns" ] ], props = { TableCellProps.Default with GridSpan = Some 2 })

A cell's own margins override the table's default the same CellMargins shape covers both:

tableCell ([ para [ run "Extra padding" ] ], props = { TableCellProps.Default with Margins = Some { CellMargins.Default with Top = Some 8.0; Bottom = Some 8.0 } })

A custom table style (TableStyleDefinition) lives in Document.TableStyles and is applied by name, the same way a built-in like "TableGrid" is - here with a bold white header row on a blue background, an italic last row, and alternating row shading, plus a table-wide default cell margin and a row that repeats on every page:

let corporateStyle: TableStyleDefinition =
    { TableStyleDefinition.Default with
        Id = "Corporate"
        Name = "Corporate"
        FirstRow =
            { TableStyleRegion.None with
                RunFormat = Some { RunStyle.Default with Bold = true; Color = Some Color.white }
                CellShading = Some(Rgb(0x4Fuy, 0x81uy, 0xBDuy)) }
        LastRow = { TableStyleRegion.None with RunFormat = Some { RunStyle.Default with Italic = true } }
        BandedRow = { TableStyleRegion.None with CellShading = Some(Rgb(0xDCuy, 0xE6uy, 0xF1uy)) } }

document
    [ section
          [ table (
                [ tableRow ([ tableCell [ para [ run "Item" ] ]; tableCell [ para [ run "Qty" ] ] ], repeatAsHeader = true)
                  tableRow [ tableCell [ para [ run "Widgets" ] ]; tableCell [ para [ run "12" ] ] ] ],
                [ 200.0; 100.0 ],
                style = { TableStyleRef.Default with Name = "Corporate" },
                cellMargins = { Top = Some 4.0; Bottom = Some 4.0; Left = Some 6.0; Right = Some 6.0 }
            ) ] ]
|> withTableStyles [ corporateStyle ]

TableStyleDefinition also covers FirstColumn/LastColumn, BandedColumn, and the four corner cells (NorthEastCell/NorthWestCell/SouthEastCell/SouthWestCell) - the two regions not modeled are each banding axis's second band, since in practice that's just WholeTable's own background showing through (see MAPPING.md).

Sections carry their own page setup - a document is a sequence of Sections, mapping 1:1 onto real Word section breaks. BreakType is how a section begins relative to the previous one - meaningless (and not written) on the very first section:

let landscape = { SectionProperties.Default with Orientation = Landscape }
document [ sectionWith landscape [ para [ run "A landscape-oriented page." ] ] ]

let continuous = { SectionProperties.Default with BreakType = ContinuousBreak }
document
    [ section [ para [ run "Section 1." ] ]
      sectionWith continuous [ para [ run "Section 2 - no page break from section 1." ] ] ]

Footnotes and endnotes mark a point in a paragraph's own Inlines - content is the note's own body, written to word/footnotes.xml/endnotes.xml with an id Writer assigns automatically (the reference-mark run itself is generated for you, on both ends):

para
    [ run "This claim needs a citation"
      footnote "Smith, J. (2023). A Study of Claims."
      run ", and this one refers to a fuller discussion"
      endnote [ para [ run "See the appendix for the full derivation." ] ] ]

A section's own footnote/endnote numbering (w:footnotePr/w:endnotePr) - None is Word's own default (continuous decimal from 1); here footnotes are lower-roman and restart every page, matching a common legal-document convention:

sectionWith
    { SectionProperties.Default with FootnoteNumbering = Some { Format = LowerRomanFormat; StartAt = None; Restart = RestartEachPage } }
    [ para [ run "Body text."; footnote "A footnote numbered i, ii, iii, ... restarting each page." ] ]

Headers and footers are per-section, with Default/First/Even variants (the titlePg/evenAndOddHeaders flags real Word needs are set automatically):

let footer = { HeaderFooterSet.None with Default = Some [ para [ run "Page "; Field("PAGE", Some "1") ] ] }
sectionWith { SectionProperties.Default with Footer = Some footer } [ para [ run "Body text." ] ]

Comments and bookmarks wrap inline content directly, the common single-paragraph case:

para [ comment ([ run "This figure needs review." ], "Please double check the totals.", author = "Alex") ]

Either spanning more than one paragraph uses two independent markers placed directly in separate paragraphs instead, sharing an id - BookmarkRangeStart/BookmarkRangeEnd for bookmarks, CommentRangeStart/CommentRangeEnd for comments (which carries the comment's own metadata on its Start, since there's no wrapping case here to hang it off - see MAPPING.md on why that id is write-time-only, unlike a bookmark's own name):

document
    [ section
          [ para [ BookmarkRangeStart "Section2"; run "This paragraph starts the bookmark" ]
            para [ run "and this one ends it."; BookmarkRangeEnd "Section2" ] ] ]

document
    [ section
          [ para [ CommentRangeStart("review1", "Alex", None, None, "This section needs review."); run "Comment starts here" ]
            para [ run "and ends here."; CommentRangeEnd "review1" ] ] ]

Track changes (inserted/deleted) wrap inline content the same way, marking it as inserted or deleted under an author and date; a whole inserted or deleted paragraph (rather than just some of its content) uses para's own markRevision instead, for the paragraph's closing mark:

para
    [ run "The quick "
      inserted ([ run "brown " ], "Alex")
      run "fox jumps over the "
      deleted ([ run "lazy " ], "Alex")
      run "dog." ]

para ([ run "This whole paragraph was inserted." ], markRevision = { Kind = Inserted; Author = "Alex"; Date = None })

Content controls (contentControl for run-level, contentControlBlock for block-level) wrap their own currently-displayed content the same way, plus a ContentControlType (see MAPPING.md for the full set - plain text, rich text, dropdown/combo box, date picker, checkbox):

para
    [ run "Client name: "
      contentControl ([ run "Type here" ], PlainTextControl false, alias = "Client Name", tag = "clientName") ]

para
    [ run "Favorite color: "
      contentControl ([ run "Blue" ], DropDownControl([ "Red", "red"; "Green", "green"; "Blue", "blue" ], false)) ]

contentControlBlock([ para [ run "This whole paragraph is a rich-text content control." ] ], RichTextControl, alias = "Notes")

Document-level protection, macros, and core properties are all pipe-friendly, same shape as Excel's own withProtection/withVbaProject:

document [...] |> withProtection { Edit = Some ReadOnlyRestriction; Password = Some "hunter2" }
document [...] |> withVbaProject (System.IO.File.ReadAllBytes("vbaProject.bin"))
document [...] |> withDocumentProperties { DocumentProperties.Default with Title = Some "Quarterly Report"; Author = Some "Kookerella" }

Save the result with a .docm path - Document.save/saveToStream automatically switch the file's own declared content type to Word's macro-enabled kind whenever a VbaProject is present, but real Word also expects the .docm extension to trust and run macros at all.

Regenerating a file as F# source

Given a Document (typically one you just Document.loaded from an existing file), Document.generateScript renders it back out as a self-contained .fsx script that rebuilds an equivalent file when run - a code-generating counterpart to Document.load:

let doc = Document.load "input.docx"

let referenceLines =
    [ "#r \"path/to/Kookerella.FsWordDsl.dll\""
      "#r \"path/to/DocumentFormat.OpenXml.dll\"" ]

let script = Document.generateScript referenceLines "output.docx" doc
System.IO.File.WriteAllText("regenerate.fsx", script)

Running dotnet fsi regenerate.fsx produces output.docx - not byte-identical to the original (zip metadata/timestamps differ) but structurally equivalent through the same round-trip lens every other test in this repo uses. Every scenario under tests/ Kookerella.FsWordDsl.Tests/Examples/ has a committed script.fsx generated exactly this way; the Category=Slow test group actually executes each one via dotnet fsi and checks it reproduces the committed .docx.

The C# wrapper

Kookerella.CsWordDsl is an idiomatic, immutable, fluent C# wrapper over the F# core, for callers who'd rather not touch F# discriminated unions or option types directly. Every F# type has a C# mirror: plain records with With*/factory-method builders for product types, enums for parameterless choices, and sealed record closed hierarchies (abstract record base, private constructor, nested cases) for everything else - the same "sealed hierarchy" pattern the Excel repo's own Kookerella.CsOpenXmlDsl uses for CellValue/ ConditionalFormatRule. Reference Kookerella.CsWordDsl instead of Kookerella.FsWordDsl and never see an FSharpOption:

using Kookerella.CsWordDsl;

var doc = Document.Create(
    Section.Of([
        Block.Paragraph([new Inline.Run("Quarterly Report")], styleId: "Title"),
        Block.Paragraph([
            new Inline.Run("This report covers "),
            new Inline.Run("Q1 2026", new RunStyle { Bold = true }),
            Inline.HyperlinkText("full dataset", new HyperlinkTarget.ExternalUrl("https://example.com/data")),
            new Inline.Run(" for details.")
        ])
    ]));

DocumentIO.Save(doc, "report.docx");
var loaded = DocumentIO.Load("report.docx");

Content controls, tables, track changes, comments, and every other feature the F# core models are covered the same way - see tests/Kookerella.CsWordDsl.Tests/DocumentTests.cs for a worked example per feature. CsCodeGen.Generate is the C# analog of Document.generateScript: it renders a Document back out as a self-contained C# file targeting .NET's "file-based apps" feature (dotnet run --file script.cs), rather than an .fsx script:

var script = CsCodeGen.Generate(["#:project path/to/Kookerella.CsWordDsl.csproj"], "output.docx", loaded);
File.WriteAllText("regenerate.cs", script);

DocumentIO also exposes the F# core's other two ways in and out directly - schema-backed XML/JSON (ToXml/FromXml, ToJson/FromJson) and F# script generation (GenerateFSharpScript, CsCodeGen.Generate's F#-targeting sibling) - so a C# caller never needs its own reference to Kookerella.FsWordDsl to reach any of the F# core's four I/O surfaces from C#.

One design note worth stating explicitly: this wrapper's records use IReadOnlyList<T> properties, and C#'s compiler-synthesized record equality does not deep-compare list contents (two records holding equal-but-distinct list instances compare unequal via plain .Equals()) - the same limitation Kookerella.CsOpenXmlDsl's own records have. Don't rely on whole-Document equality in your own code; compare the specific values you care about, the same way this repo's own DocumentTests.cs does.

XML

Xml.toDocument/Xml.ofDocument (in Xml.fs) are a third way in and out of the DSL, alongside writing F# directly and code generation: plain XML, against a real schema (Xml.xsd, embedded in the assembly). A data-carrying DU case becomes an element named after the case; a parameterless-choice case becomes an attribute value or bare string, matching the convention the Excel repo's own Xml.fs documents.

open System.Xml.Linq

// XML -> Document -> .docx
let doc = XElement.Load "report.xml" |> Xml.ofDocument
Document.save "report.docx" doc

// .docx -> Document -> XML
let xml = Document.load "report.docx" |> Xml.toDocument
xml.Save "report.xml"

A run with direct formatting and a hyperlink, in XML:

<para>
  <run>Visit </run>
  <hyperlink tooltip="Kookerella on GitHub">
    <externalHyperlink>https://github.com/Kookerella-Ltd</externalHyperlink>
    <content>
      <run styleId="Hyperlink">Kookerella on GitHub</run>
    </content>
  </hyperlink>
  <run> for more.</run>
</para>

Xml.schemaSet() loads the compiled schema for validating either direction yourself (XDocument.Validate) - every scenario under tests/Kookerella.FsWordDsl.Tests/Examples/ has a committed document.xml validated against it this way as part of the same test that generates it.

toDocument's output is deterministically ordered (Styles/Numbering/TableStyles sorted by Id, regardless of the order the underlying Document's own lists happen to be in) - paragraph/run content is already real document order and needs no sorting, but these three are ID-referenced catalogs whose own list order carries no meaning, so this is what makes committing document.xml to version control and diffing it across commits actually meaningful: a genuine content change produces a small, isolated diff rather than a spurious one from a catalog getting reshuffled between two otherwise-identical documents.

JSON

Json.toDocument/Json.ofDocument (in Json.fs) are a fourth way in and out of the DSL, alongside writing F# directly, code generation, and XML: plain JSON, for a caller whose tooling speaks JSON rather than XML. The same DU-case conventions apply, in JSON's own idiom (a single-key object for a data-carrying case, a bare string for a parameterless one):

open System.Text.Json.Nodes

// JSON -> Document -> .docx
let doc = JsonNode.Parse(File.ReadAllText "report.json").AsObject() |> Json.ofDocument
Document.save "report.docx" doc

// .docx -> Document -> JSON
let json = Document.load "report.docx" |> Json.toDocument
File.WriteAllText("report.json", json.ToJsonString())

The same hyperlink example as above, in JSON:

{
  "para": {
    "inlines": [
      { "run": { "text": "Visit " } },
      {
        "hyperlink": {
          "target": { "externalHyperlink": "https://github.com/Kookerella-Ltd" },
          "runs": [ { "run": { "text": "Kookerella on GitHub", "styleId": "Hyperlink" } } ],
          "tooltip": "Kookerella on GitHub"
        }
      },
      { "run": { "text": " for more." } }
    ]
  }
}

The same determinism Xml.toDocument has (Styles/Numbering/TableStyles sorted by Id) applies to Json.toDocument's output too, for the same reason: a genuine content change produces a small, isolated diff rather than a spurious one from a catalog getting reshuffled between two otherwise-identical documents.

Unlike XML, .NET has no built-in JSON Schema validator, so Json.schema.json (in the repo) is validated only from this repo's own test suite (via a test-only JsonSchema.Net dependency) rather than exposed as a public API - see Json.fs's own doc comment.

Building and testing

dotnet build
dotnet test --filter "Category!=Slow"
dotnet run --project samples/Kookerella.FsWordDsl.Sample

The default loop above skips the slow Category=Slow tests, which actually invoke dotnet fsi on every generated Examples/*/script.fsx (multi-second process startup each). Run those explicitly, after the fast suite has populated the .fsx files at least once:

dotnet test --filter "Category=Slow"

Plain dotnet test (no filter) runs both groups.

The C# wrapper's own suite has no fast/slow split - CsCodeGenTests.cs shells out to dotnet run --file itself, so a single run already covers the C# analog of the F# suite's slow group:

dotnet test tests/Kookerella.CsWordDsl.Tests

Available Tools

10 tools
create_documentA

Creates a new Word document (.docx) from a simple list of paragraph texts and saves it to disk. Each string becomes one plain paragraph, in order - no formatting, styles, tables, or images in this version. Does not support run styling, tables, images, headers/footers, or track changes - reference the Kookerella.FsWordDsl library directly for those.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesOutput file path, e.g. "C:\reports\memo.docx". The directory must already exist.
paragraphsYesThe paragraphs to create, in order. Each element becomes one plain paragraph.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral disclosure burden. It does well by revealing that output is a plain .docx, that paragraphs are created in order, and that many Word features are unsupported. It does not mention overwrite behavior or return value, but for this simple creation tool the disclosed limitations are significant and useful.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with no fluff: the purpose is stated first, followed by clear limitations and an alternative reference. Every sentence earns its place and the description is appropriately sized for the tool's simplicity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple two-parameter creation tool, the description plus schema covers the essential behavior, limitations, and disk output. It does not describe return value or overwrite semantics, but given the output schema is absent and the function is straightforward, the definition is largely complete. Mentioning the XML-based sibling could have improved routing further.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents both path and paragraphs. The description adds the contextual note that each string becomes one plain paragraph in order, which reinforces the schema but does not add major new semantics beyond it.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('creates') and resource ('new Word document (.docx)') along with the exact input form (a simple list of paragraph texts) and output behavior (saves it to disk). It also differentiates from siblings like create_document_from_xml by explicitly limiting the scope to plain paragraphs with no formatting, styles, tables, or images.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It clearly implies use for simple paragraph-only documents and explicitly calls out unsupported features (run styling, tables, images, headers/footers, track changes), directing users to the underlying library for those cases. It could be stronger by naming a specific sibling tool like create_document_from_xml as the alternative for richer documents, but the guidance is still clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_document_from_jsonA

Builds a new Word document from JSON matching the shape generate_json produces (see the main library repo's Json.schema.json) and saves it to disk - the inverse of generate_json. The JSON-side equivalent of create_document_from_xml, for a caller that already produces data as JSON and wants to reach Word without learning the OOXML schema, this library's own F#/C# API, or needing .NET installed at all - the .NET work happens inside this server, so any language can call it directly, and a human with no MCP client at all can get the same result via fsworddsl-mcp build from a plain shell. Covers the same section/paragraph-level feature set generate_json does; unlike create_document, this isn't limited to plain paragraph text - named styles, tables, images, and every other modeled feature can be expressed in the JSON.

ParametersJSON Schema
NameRequiredDescriptionDefault
jsonYesThe document JSON content - an object matching Json.schema.json's root shape.
pathYesOutput file path, e.g. "C:\reports\memo.docx". The directory must already exist.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the behavioral burden. It clearly states this is a filesystem write operation, mentions that .NET execution happens server-side, and notes the CLI-equivalent path. It does not disclose overwrite semantics or error behavior, but the core side effect is unambiguous.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single long, winding sentence with several embedded clauses and parenthetical asides, making it harder to parse quickly. The core action is front-loaded and the content is dense, but it would benefit from being split into a couple of crisp sentences.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple two-parameter tool with no output schema, the description covers the input contract, output destination, feature parity, and relationship to siblings. It does not explain overwrite behavior or return values, but those are secondary to correct invocation given the clear write-to-disk contract.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage for the two parameters is 100%, so the baseline is 3. The description adds meaning by tying the json parameter to generate_json's output shape, enumerating supported features like named styles, tables, and images, and confirming path is the disk target.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific action and resource: "Builds a new Word document from JSON... and saves it to disk." It also clearly separates this tool from siblings by calling it "the inverse of generate_json," "the JSON-side equivalent of create_document_from_xml," and by contrasting it with create_document's plain-text-only limitation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says when to use this tool: for callers who already produce JSON and want Word output without learning OOXML, the F#/C# API, or needing .NET installed. It also names alternatives and distinguishes feature coverage, so an agent can route between this tool, create_document_from_xml, and create_document.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_document_from_xmlA

Builds a new Word document from XML matching Kookerella.FsWordDsl's own embedded schema (Xml.xsd) and saves it to disk - the inverse of generate_xml. The natural target for a caller that already produces data as XML (e.g. an XSLT pipeline generating a report) and wants to reach Word without learning the OOXML schema, this library's own F#/C# API, or needing .NET installed at all - like generate_xml, the .NET work happens inside this server, so any language can call it directly, and a human with no MCP client at all can get the same result via fsworddsl-mcp build from a plain shell. Covers the same section/paragraph-level feature set generate_xml does; unlike create_document, this isn't limited to plain paragraph text - named styles, tables, images, and every other modeled feature can be expressed in the XML.

ParametersJSON Schema
NameRequiredDescriptionDefault
xmlYesThe document XML content - a <document> root element matching Xml.xsd.
pathYesOutput file path, e.g. "C:\reports\memo.docx". The directory must already exist.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure, and it does so well: it states the side effect of saving to disk, places execution server-side, notes that .NET is not needed by the caller, and gives the CLI equivalent. It does not explicitly describe return values or error/validation behavior, but it is otherwise transparent about what the tool does and how it operates.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core purpose is front-loaded, and the comparisons with generate_xml and create_document are valuable. However, the description is long and includes tangential details such as the plain-shell CLI path and the fact that any language can call it; these are useful context but could be trimmed without losing essential selection and invocation guidance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity and the absence of annotations and output schema, the description is largely complete: it explains when to use it, what feature set it covers, how it relates to siblings, and where the heavy lifting happens. The only real gaps are explicit return-value expectations and behavior on validation failure or file overwrite, but these do not severely undermine the agent's ability to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the input schema already fully documents the xml and path parameters. The description reinforces that the XML must match Xml.xsd and that the output is a Word document saved to disk, but it does not add substantial parameter-level meaning beyond what the schema provides. The baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Builds a new Word document from XML ... and saves it to disk.' It also distinguishes this tool from close siblings by calling it the inverse of generate_xml and by contrasting it with create_document, which is limited to plain paragraph text. This makes the tool's purpose and scope immediately clear.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit guidance on when to use this tool: when a caller already produces XML, wants to reach Word without learning OOXML, the library API, or .NET, and needs the full feature set including styles, tables, and images. It names alternatives (generate_xml and create_document) and even clarifies the exclusions: unlike create_document, this tool is not limited to plain text.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

generate_csharp_scriptA

Reads an existing Word document and returns a self-contained C# file (using Kookerella.CsWordDsl) that rebuilds an equivalent file when run via dotnet run <file>.cs (.NET 10's file-based apps feature - no .csproj needed). The C# equivalent of generate_fsharp_script, for a caller who wants pasteable/ runnable C# rather than F# - useful for explaining how a file is structured, or as a starting point for the wrapper's fluent API (named styles, tables, images, headers/footers, comments, track changes, content controls, protection, etc. - Kookerella.CsWordDsl covers the same feature set as generate_fsharp_script's own Kookerella.FsWordDsl) beyond what create_document exposes.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesPath to an existing .docx/.docm file to reverse-engineer into C# source.
outputFileNameYesThe output filename the generated script should save its rebuilt file to, e.g. "output.docx".

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the burden of disclosing behavior. It clearly states that the tool reads an existing document and returns generated C# code, and adds useful context about the DSL, .NET 10 file-based apps, and no-.csproj requirement. It does not discuss edge cases, errors, or whether the input document remains untouched, but the core behavior is transparent enough for a safe read-and-generate operation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The main action is front-loaded and the description provides rich context, but the second sentence is quite long with several parentheticals and slightly repetitive DSL comparison. Still, nearly every piece of information earns its place by helping the agent understand the tool's output and relationship to siblings.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given there is no output schema, the description does well to explain what the returned artifact is, how it is meant to be executed, and what feature areas it covers. It could be more complete about return format or failure behavior, but for a generation tool with fully documented parameters, it is sufficiently complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so both parameters are already documented in the schema. The description does not add any parameter-specific guidance beyond what the schema provides, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific operation: reads an existing Word document and returns a self-contained C# file that rebuilds an equivalent document. It also explicitly contrasts itself with generate_fsharp_script and create_document, making its role distinct from sibling tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly identifies generate_fsharp_script as the alternative and gives the selection criterion: C# vs F#. It also explains when the tool is useful (explaining file structure, as a starting point for fluent API) and notes it goes beyond what create_document exposes, giving an agent clear guidance on when to choose it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

generate_fsharp_scriptA

Reads an existing Word document and returns a self-contained F# script (using Kookerella.FsWordDsl) that rebuilds an equivalent file when run via dotnet fsi. Useful for explaining how a file is structured, or as a starting point for a caller who wants the library's full feature set (named styles, tables, images, headers/footers, comments, track changes, content controls, etc.) beyond what create_document exposes.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesPath to an existing .docx/.docm file to reverse-engineer into F# source.
outputFileNameYesThe output filename the generated script should save its rebuilt file to, e.g. "output.docx".

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full behavioral disclosure burden. It communicates that the tool reads an existing file, that it does not modify the input (it reverse-engineers), and that the output is a script requiring dotnet fsi to run. It also discloses the generated script's save target via outputFileName. It does not mention side effects, but the read-only nature is implied by 'Reads an existing Word document... reverse-engineer'.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, dense paragraph that front-loads the core action (reads a document, returns a script) and then provides the purpose and differentiation. Every sentence earns its place, with no filler or repetition of the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 2 required params with full schema coverage, no output schema, and no nested objects, the description covers the essential context: input, output, runtime requirement (dotnet fsi), and differentiation from create_document. It doesn't explicitly mention error cases or file access implications, but for a read-and-generate tool with this schema, the description is sufficiently complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents both parameters. The description adds context to outputFileName by explaining that it is the filename the generated script should save its rebuilt file to, reinforcing the parameter's role in the generated output. This goes slightly beyond the schema's own description, which is helpful for an agent.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states a specific verb ('Reads... and returns'), a specific resource (existing Word document), and the output (self-contained F# script). It also distinguishes itself from create_document by explaining it exposes the full library feature set, which differentiates it from sibling tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear usage context: useful for explaining a file's structure or as a starting point for full-feature generation. It implicitly contrasts with create_document, which is a key alternative among siblings, though it does not explicitly say 'when not to use this tool'. The intended use cases are clear enough for an agent to select it appropriately.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

generate_jsonA

Reads an existing Word document and returns it as JSON. The JSON-side equivalent of generate_xml, for a caller whose tooling speaks JSON rather than XML - same use cases (inspect, transform, or archive a document's structure without any F#/C# source, or any .NET runtime at all, on the caller's side) and the same section/paragraph-level feature set. Usable directly from Python, JavaScript, or any other language, and a human with no MCP client at all can get the same result via fsworddsl-mcp convert <file> --lang json from a plain shell. Unlike generate_xml, there's no runtime JSON Schema validation built into the core library itself (see generate_json_schema's own doc string for why) - but generate_json_schema still returns the documented shape this produces.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesPath to an existing .docx/.docm file to convert to JSON.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full behavioral burden, and it delivers: it discloses the read-only nature ('Reads an existing Word document'), the lack of runtime JSON Schema validation in the core library, and that no .NET runtime is needed on the caller's side. It honestly points to generate_json_schema for the documented output shape. The only gap is unstated error behavior for invalid or unreadable files, which is minor for a read/conversion tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core purpose is front-loaded in the first sentence, followed by sibling routing, portability, a CLI alternative, and a validation caveat — each sentence earns its place. It is somewhat verbose with long nested clauses, so it could be tightened, but the logical structure is strong.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter conversion tool with no output schema and no annotations, the description covers purpose, when-to-use, cross-language usability, and the key behavioral caveat. Delegating output-shape documentation to generate_json_schema is a legitimate division of labor given that sibling exists. Nothing an agent needs to select and invoke this tool correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% — the single path parameter is fully documented with type and meaning ('existing .docx/.docm file to convert to JSON'). The description reinforces the 'existing file' constraint but adds no new parameter-level meaning, so the schema-does-the-heavy-lifting baseline of 3 is correct.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence states a specific verb, resource, and output: reads an existing Word document and returns it as JSON. It explicitly positions itself as the JSON-side equivalent of generate_xml, distinguishing it from the most confusable sibling at a glance. No ambiguity about what this tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Names generate_xml as the alternative and gives the selection criterion: callers whose tooling speaks JSON rather than XML. It lists the shared use cases (inspect, transform, archive) and even offers a CLI fallback for humans without an MCP client. This is explicit routing guidance rather than something left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

generate_json_schemaA

Returns the raw JSON Schema (Json.schema.json) that generate_json's output and create_document_from_json's input both conform to. Meant for a caller authoring JSON by hand or by a generation script who wants real schema validation/autocomplete in their own editor or pipeline, rather than reverse-engineering the shape from a generate_json example. This schema isn't validated against at runtime by the core library itself the way Xml.xsd is (JSON Schema has no .NET-built-in equivalent to System.Xml.Schema, so wiring that up would mean adding a runtime dependency - JsonSchema.Net - to every consumer of the core library just for this) - it's bundled here, in the Mcp tool specifically, purely to hand back on request.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full transparency burden. It discloses that the schema is not validated against at runtime, is bundled in the MCP tool rather than the core library, and exists purely to be handed back on request. This prevents false expectations about validation behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The most important information is front-loaded: what is returned, for whom, and why. The third sentence adds a useful caveat about runtime validation, but the .NET/JsonSchema.Net implementation aside makes it slightly more verbose than necessary.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter tool with no output schema, the description is complete. It covers the return value, the relationship to related tools, the intended use, and an important limitation, so an agent has everything needed to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has zero parameters, so the description has no parameter semantics to add. Per the baseline rule for no-parameter tools, this is adequate; the description correctly focuses on return behavior instead.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Returns the raw JSON Schema (Json.schema.json)'. It further identifies the schema's relationship to generate_json and create_document_from_json, which clearly distinguishes it from the XML-schema sibling and from example generation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly names the intended audience and use case: callers authoring JSON by hand or script who want validation/autocomplete, rather than reverse-engineering from a generate_json example. It does not enumerate explicit 'when not to use' scenarios, but the context is clear enough.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

generate_xmlA

Reads an existing Word document and returns it as XML, validated against Kookerella.FsWordDsl's own embedded schema (Xml.xsd). A plain-data alternative to generate_fsharp_script/generate_csharp_script for a caller who wants to inspect, transform (e.g. via XSLT), or archive a document's structure without any F#/C# source involved - and without any .NET runtime on the caller's side either: the .NET work happens inside this server, so Python, JavaScript, or any other language can call this tool directly, and a human with no MCP client at all can get the same result via fsworddsl-mcp convert <file> --lang xml from a plain shell. Unlike those two, this returns data, not a runnable script, so there is no output-filename parameter to control what a rebuild saves as. Covers the same section/paragraph-level feature set generate_fsharp_script does.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesPath to an existing .docx/.docm file to convert to XML.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It explains server-side .NET execution, client-side language independence, validation against an embedded schema, CLI availability, and the absence of an output-filename parameter. This gives the agent a clear picture of what happens and what the tool does not do.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose, then efficiently covers usage guidance, cross-language accessibility, and sibling differentiation. Every sentence adds meaningful information, including the CLI fallback and feature-set comparison, with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with one required parameter, no output schema, and no annotations, the description is highly complete. It explains what the tool returns, how it validates, what makes it different from siblings, and how it can be invoked from various environments. Nothing essential for correct invocation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the single 'path' parameter is already described as 'Path to an existing .docx/.docm file to convert to XML.' The tool description reinforces that the input is an existing Word document, but adds no new parameter-level semantics beyond what the schema already provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence states a specific action, resource, and output: reads an existing Word document and returns it as XML. It also distinguishes itself from sibling code-generation tools by explicitly naming generate_fsharp_script and generate_csharp_script as alternatives and clarifying it returns data rather than a runnable script.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description is explicit about when to use this tool: when the caller wants to inspect, transform (e.g., via XSLT), or archive a document without F#/C# source and without a .NET runtime. It also states what it is not for, noting that unlike the script generators, it returns data and has no output-filename parameter.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

generate_xml_schemaA

Returns the raw XSD (Xml.xsd) that generate_xml's output and create_document_from_xml's input both conform to. Meant for a caller authoring XML by hand or by transform (e.g. an XSLT stylesheet) who wants real schema validation/autocomplete in their own editor or pipeline, rather than reverse- engineering the shape from a generate_xml example.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses that the tool is a simple retrieval ('Returns the raw XSD'), implies a read-only behavior, and adds useful context about the schema's role. It does not discuss potential response formatting or side effects, but for a zero-parameter accessor this is adequate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with no redundancy. It front-loads the core behavior in the first sentence and adds practical use-case context in the second, making every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (no parameters, no output schema), the description is complete. It explains what is returned, why the XSD is useful, and how it relates to generate_xml and create_document_from_xml, so an agent can correctly select and invoke it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters and schema coverage is trivially 100%, so the baseline is 4. The description adds no param details because none are needed; it is clear that no input is required.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Returns') and names the exact resource (the raw XSD, Xml.xsd). It clearly distinguishes the tool from siblings by explaining that the schema is shared by generate_xml's output and create_document_from_xml's input, and by positioning it as a schema-authoring aid rather than a generation tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly states when to use this tool: when authoring XML by hand or via transform and wanting schema validation/autocomplete. It also gives an alternative ('rather than reverse-engineering the shape from a generate_xml example'), which helps an agent decide between this and another approach.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_documentA

Reads an existing Word document (.docx/.docm) and returns its paragraphs as a JSON array of plain strings, one per paragraph, matching create_document's own input convention. Features outside the plain-paragraph model (run styling, tables, images, headers/footers, comments, track changes, content controls, etc.) are not included in this output - see MAPPING.md in the main library repo for the full list of what round-trips. Table content and non-paragraph blocks are skipped entirely.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesPath to an existing .docx or .docm file.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full disclosure burden. It does well by revealing the exact return format, the round-trip convention, and the major excluded feature categories plus where to find the full list. It does not mention error behavior for nonexistent paths or unreadable files, which is a minor gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is efficiently structured: the first sentence states the core behavior and return format, the second provides useful limitations, and the third emphasizes the table/non-paragraph skip rule. No sentence is wasted, and the key information is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has only one parameter, no output schema, and no annotations, the description is sufficiently complete: it states what the tool does, what it returns, which document types are accepted, and which content is omitted. An agent has enough information to decide whether to call it and what to expect from its response.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already describes 'path' as 'Path to an existing .docx or .docm file', so schema coverage is 100%. The description adds context about the expected file type and that the document must already exist, but it does not materially expand beyond the schema's parameter description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Reads'), a specific resource ('existing Word document (.docx/.docm)'), and precisely describes the output ('JSON array of plain strings, one per paragraph'). It also ties the output convention to create_document, which distinguishes the read behavior from the create-oriented sibling tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly implies when to use the tool: to read an existing Word document as plain paragraphs. It also gives explicit when-not guidance by stating that tables, non-paragraph blocks, and rich features like styling or track changes are skipped entirely. However, it does not name an explicit alternative tool for those unsupported features, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 10 tool updatesv0.1.0
    • First observedcreate_document
    • First observedcreate_document_from_json
    • First observedcreate_document_from_xml
    • First observedgenerate_csharp_script
    • First observedgenerate_fsharp_script
    • First observedgenerate_json
    • First observedgenerate_json_schema
    • First observedgenerate_xml
    • First observedgenerate_xml_schema
    • First observedread_document

TDQS

A4.4/5.0
Disambiguation5/5

Each tool targets a distinct input/output format: plain text, XML, JSON, F# script, C# script, or schema, so an agent can select based on the caller's data format and desired output. The inverse pairs (read vs create) are clearly labeled, and the schema tools are distinct from the document-generation tools.

Naming Consistency5/5

Tool names consistently follow a verb_noun pattern, with create_* for writing documents, generate_* for extracting representations or schemas, and read_* for simple plain-text reading. The from_xml/from_json suffixes make format-specific variants predictable and easy to compare.

Tool Count5/5

Ten tools is well-scoped for a Word document server covering creation, reading, multiple serialization formats, and schema retrieval. Each tool serves a clearly identifiable consumer need, and the count is not bloated.

Completeness4/5

The tool surface covers full-featured round-trips for XML and JSON, plus a simple plain-text round-trip and schema generation, which covers the core authoring/inspection workflow well. Obvious gaps like updating or converting an existing document between representations are minor given the server's stated purpose of building and inspecting .docx files.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • -
    license
    B
    quality
    Not graded
    maintenance
    Enables AI assistants to create, read, and manipulate Microsoft Word documents with comprehensive formatting, table creation, content management, and document protection capabilities. Supports advanced operations like merging documents, PDF conversion, and rich text formatting through a standardized interface.
    32
    -
  • A
    license
    Not graded
    quality
    F
    maintenance
    Enables AI agents to create, read, modify, and convert Word documents without Microsoft Word, supporting 18 tools for document operations, paragraph manipulation, table management, formatting, and conversion between multiple formats including PDF, HTML, and Markdown.
    6
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to read, edit, and create Microsoft Word documents (.docx) with support for rich text, tables, and images, deployable locally or via SSE.
    3
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Kookerella-Ltd/Kookerella.FsWordDsl'

If you have feedback or need assistance with the MCP directory API, please join our Discord server