Word MCP Server
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-relativeThemecolor - seeThemeColorKind),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, withBasedOninheritance) and a smallBuiltInStylescatalog (normal,heading1/2/3,title,listParagraph,hyperlinkCharStyle).Numbering.fs-NumberFormatKind,ListLevel,NumberingDefinitionfor numbered/bulleted lists, including multi-level ones (ListLevelisn't limited to one per definition - seeBuilders.multiLevelNumberedListDef).Hyperlinks.fs-HyperlinkTarget(external URL vs. internal bookmark reference).Protection.fs-EditRestrictionandDocumentProtection, document-level (Word has no per-section equivalent of Excel's per-sheet protection).Revisions.fs-RevisionKind/Revisionfor track changes (Inline.TrackedChange,Paragraph.MarkRevision) - narrowly scoped to inserted/deleted content and paragraph marks, seeMAPPING.mdfor what isn't covered.ContentControls.fs-ContentControlType/ContentControlPropsfor content controls (structured document tags,w:sdt): plain text, rich text, dropdown/combo box, date picker, checkbox - seeMAPPING.mdfor 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-cellMarginsoverride),TableStyleRef,TableStyleRegion/TableStyleDefinition(custom table style definitions - all thirteen of OOXML's conditional-formatting regions), andCellMargins(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-paragraphBookmark/Commentcases and the cross-paragraphBookmarkRangeStart/End/CommentRangeStart/Endmarkers, simple fields, footnotes/endnotes,TrackedChangefor track changes, andInlineContentControlfor content controls),Paragraph(includingMarkRevision),Block(paragraph, table, orContentControlBlock- the block-level counterpart toInlineContentControl),TableCell/TableRow(includingRepeatAsHeader)/TableEntry(includingCellMargins),HeaderFooterSet,SectionProperties(includingBreakTypeandFootnoteNumbering/EndnoteNumbering),Section,Document(includingDocument.VbaProject, a macro-enabled document's rawvbaProject.binbytes,Document.Properties, andDocument.TableStyles).Xml.fs/Xml.xsd- the XML surface:Xml.toDocument/Xml.ofDocumenttranslate aDocumentto/from anXElementtree, andXml.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.ofDocumenttranslate aDocumentto/from aSystem.Text.Json.Nodes.JsonObjecttree. 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) plusDocumentDsl- smart constructors (run,para(withmarkRevision),hyperlink,bookmark,comment,inserted/deleted(track changes),contentControl/contentControlBlock(content controls),image,footnote,endnote,tableCell,tableRow(withheight/repeatAsHeader),table(withstyle/borders/cellMargins)) with real optional parameters, the Word analog of the Excel repo'sSheetDsl.Interpreter/StyleRegistry.fs- shared run/paragraph/border/color conversions plusDocument.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 aDocumentback out as a self-contained.fsxscript that rebuilds an equivalent file when run (internal).Api.fs- the publicDocument.save/saveToStream/load/loadFromStream/generateScriptentry points.
src/Kookerella.CsWordDsl- the fluent C# wrapper (see "## The C# wrapper" below): immutable records/sealed recordclosed 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 ofInterpreter/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 plainconvert/buildCLI, depending onargv). Distributed as adotnet tool(fsworddsl-mcp), same as the Excel sibling's ownKookerella.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 toExamples/<test name>/output.docx(checked into the repo), plusscript.fsx(regenerates the file - a separate, slowerCategory=Slowtest group actually executes each one viadotnet fsi),document.xml, anddocument.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-inExamples/*/output.docxfixtures rather than re-authoring every scenario a second time),CsCodeGenTests.cs(actually executes a generated file viadotnet run --file, the C# analog of the F# suite'sCategory=Slowdotnet fsigroup).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.SampleThe 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.TestsAvailable Tools
10 toolscreate_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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Output file path, e.g. "C:\reports\memo.docx". The directory must already exist. | |
| paragraphs | Yes | The paragraphs to create, in order. Each element becomes one plain paragraph. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| json | Yes | The document JSON content - an object matching Json.schema.json's root shape. | |
| path | Yes | Output file path, e.g. "C:\reports\memo.docx". The directory must already exist. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| xml | Yes | The document XML content - a <document> root element matching Xml.xsd. | |
| path | Yes | Output file path, e.g. "C:\reports\memo.docx". The directory must already exist. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Path to an existing .docx/.docm file to reverse-engineer into C# source. | |
| outputFileName | Yes | The output filename the generated script should save its rebuilt file to, e.g. "output.docx". |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Path to an existing .docx/.docm file to reverse-engineer into F# source. | |
| outputFileName | Yes | The output filename the generated script should save its rebuilt file to, e.g. "output.docx". |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Path to an existing .docx/.docm file to convert to JSON. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Path to an existing .docx/.docm file to convert to XML. |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Path to an existing .docx or .docm file. |
TDQS
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.
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.
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.
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.
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.
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.
10 tool updates
v0.1.0- First observed
create_document - First observed
create_document_from_json - First observed
create_document_from_xml - First observed
generate_csharp_script - First observed
generate_fsharp_script - First observed
generate_json - First observed
generate_json_schema - First observed
generate_xml - First observed
generate_xml_schema - First observed
read_document
TDQS
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.
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.
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.
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
Related MCP Connectors
Create real Word .docx files from your AI chat: proposals, quotes, contracts, statements of work.
Use your own Word templates to convert Markdown → DOCX/PDF/HTML from any MCP-compatible AI.
Collaborative word processor you can use with your agent.
Read and write typed fictional worlds (22 linked element types) via the OnlyWorlds open standard.
Related MCP Servers
- -licenseBqualityNot gradedmaintenanceEnables 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-
- AlicenseBqualityDmaintenanceEnables AI assistants to create and manipulate Microsoft Word documents programmatically with support for rich text formatting, tables, lists, headings, and find-and-replace operations.1027MIT
- AlicenseNot gradedqualityFmaintenanceEnables 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.6MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI agents to read, edit, and create Microsoft Word documents (.docx) with support for rich text, tables, and images, deployable locally or via SSE.3MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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