Word MCP Server (FsWordDsl) について
Word MCP Server (FsWordDsl)は、F#のドキュメントDSLによるWordファイルの読み書きと変換機能をMCPクライアントに提供し、コマンドラインでも利用できます。既存文書から再構築用スクリプトを生成でき、XMLやJSONとの変換にも対応します。
主な機能
- 01Word文書の読み込みと書き込み
- 02F#、C#、XML、JSONへの逆変換
- 03XMLまたはJSONから文書を作成
- 04リッチテキスト、表、スタイル、画像に対応
- 05変換・ビルド用コマンドライン
活用シーン
- 01既存DOCXから読み取り可能なモデルを抽出
- 02Word文書を再構築するソースコードを生成
- 03XMLまたはJSONからWordファイルを作成
- 04MCPクライアントからWord文書を処理
MCP 対応
MCPサーバーは、Word文書の読み込み、書き込み、逆変換を互換性のあるAIエージェント向けのツールとして公開します。`convert`と`build`のCLIコマンドも備えています。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
このサーバーのパッケージやリモートエンドポイントはまだ取得できていません。プロジェクトの README の手順に従ってください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
Kookerella.FsWordDsl
DocumentFormat.OpenXml SDK に対する呼び出しとして解釈される、Word 文書を構築するための型安全な F# DSL です。DSL は単純なデータモデル(構造的等価性を持つレコード/DU)で、インタープリター(Writer)がこれを OOXML にコンパイルし、逆変換(Reader)は既存の .docx を解析して同じ DSL に戻します。
Kookerella.FsOpenXmlDsl(Excel/SpreadsheetML 版)の WordprocessingML 版で、同じ目標、同じラウンドトリップの理念を、Word 独自のドキュメントモデルに適用しています。どの WordprocessingML 機能が 1 対 1 で対応し、どの機能が近似され、どの機能がまだモデル化されていないかについては、MAPPING.md をご覧ください。
双方向のラウンドトリップに対応しています。これはほとんどの Word ライブラリにはない機能です。多くのライブラリでは、ドキュメントを一から構築する命令型 API は提供されますが、既存のファイルを読み取り可能なソースに戻す方法はありません。ここでは、Reader が実際の .docx/.docm を解析して同じ DSL に戻し、さらに Document.generateScript がそのモデルを同等のファイルを再構築する自己完結型スクリプトとして出力します。これは単なるライターではなく、Word 文書の逆コンパイラーです。さらに、Xml.toDocument/Xml.ofDocument(以下の "## XML" を参照)と、Json.toDocument/Json.ofDocument(以下の "## JSON" を参照)という 2 つのインターフェイスもあり、実際のスキーマに基づいてプレーンな XML または JSON との間で同じ変換を行います。コードを書くよりもデータを生成または利用したい呼び出し元向けです。
流暢な C# ラッパー(Kookerella.CsWordDsl)は、F# の判別共用体/オプション型を直接扱いたくない呼び出し元向けに、F# コアの上に構築されています。With* ビルダーを備えたイミュータブルなレコードに加え、Document を実行可能な C# ソースとして出力する独自の CsCodeGen 逆コンパイラーも含まれています。以下の "## The C# wrapper" をご覧ください。
MCP サーバー(Kookerella.FsWordDsl.Mcp)は、同じ読み取り/書き込み/逆コンパイル機能を、MCP 対応の AI エージェントが直接呼び出せるツールとして公開します。また、MCP クライアントを使わない場合にも利用できる通常の CLI(fsworddsl-mcp convert/build)としても動作します。利用可能なツールの一覧については、専用の README をご覧ください。
.NET がインストールされていませんか? お使いのプラットフォーム(win/linux/osx、x64/arm64)向けの MCP サーバーの自己完結型ビルドをリリースページからダウンロードしてください。解凍して実行するだけで、インストールは不要です。
構成
src/Kookerella.FsWordDsl- ライブラリ。Units.fs- ポイント/インチ/ピクセルと、WordprocessingML がシリアライズに使用する物理単位(ページの寸法や間隔には twips、画像のサイズには EMU)の間の変換。Styles.fs- 文字と段落の書式設定:Color(Rgb、Auto、またはテーマに相対的なTheme色。ThemeColorKindを参照)、HighlightColor(Word 固有の固定ハイライトパレット)、UnderlineStyle、RunStyle(スモールキャップス、すべて大文字、隠しテキストを含む)、ParagraphAlignment、Indentation、LineSpacingRule、TabStopAlignment/TabLeader/TabStop、ParagraphFormat(段落の罫線、網かけ、カスタムタブストップを含む)、BorderLineStyle、BorderSide、BorderStyle(段落と表の両方の罫線に再利用)。NamedStyles.fs-StyleDefinition(BasedOnによる継承を持つ段落スタイルまたは文字スタイル)と、少数のBuiltInStylesカタログ(normal、heading1/2/3、title、listParagraph、hyperlinkCharStyle)。Numbering.fs- 番号付き/箇条書きリスト用のNumberFormatKind、ListLevel、NumberingDefinition。複数レベルのリストにも対応します(ListLevelは定義ごとに 1 つに制限されません。Builders.multiLevelNumberedListDefを参照)。Hyperlinks.fs-HyperlinkTarget(外部 URL または内部ブックマーク参照)。Protection.fs-EditRestrictionとDocumentProtection。保護はドキュメント単位です(Word には、Excel のシートごとの保護に相当するセクション単位の機能はありません)。Revisions.fs- 変更履歴用のRevisionKind/Revision(Inline.TrackedChange、Paragraph.MarkRevision)。挿入/削除されたコンテンツと段落記号に限定しています。対象外の内容についてはMAPPING.mdを参照してください。ContentControls.fs- コンテンツコントロール(構造化文書タグ、w:sdt)用のContentControlType/ContentControlProps:プレーンテキスト、リッチテキスト、ドロップダウン/コンボボックス、日付選択、チェックボックス。対象外の内容についてはMAPPING.mdを参照してください。PageSetup.fs-PageOrientation、PageSize、PageMargins、SectionBreakType、NoteNumberRestart/NoteNumberingSettings(セクション独自の脚注/文末脚注番号設定)。Tables.fs-TableBorders、VerticalMergeKind、TableCellProps(セルごとのMargins上書きを含む)、TableStyleRef、TableStyleRegion/TableStyleDefinition(カスタム表スタイル定義。OOXML の 13 種すべての条件付き書式領域に対応)、CellMargins(表の既定の余白と、単一セル独自の上書きに共通する形状)。Images.fs-ImageFormat、ImageEntry(ファイルの生バイト列とページ上のサイズ)。ラン内にインラインでアンカーされます。DocumentProperties.fs-DocumentProperties(Title、Author、Subject、Keywords、Comments、Category、Company)。ドキュメントのコアメタデータで、Document.Propertiesに格納されます。Model.fs- 再帰的なコンテンツモデル:Inline(ラン、改行、画像、ハイパーリンク、ブックマーク、コメント。単一段落内のBookmark/Commentケースと、段落をまたぐBookmarkRangeStart/End/CommentRangeStart/Endマーカーの両方、単純フィールド、脚注/文末脚注、変更履歴用のTrackedChange、コンテンツコントロール用のInlineContentControlを含む)、Paragraph(MarkRevisionを含む)、Block(段落、表、またはContentControlBlock。InlineContentControlに対応するブロックレベルの要素)、TableCell/TableRow(RepeatAsHeaderを含む)/TableEntry(CellMarginsを含む)、HeaderFooterSet、SectionProperties(BreakTypeとFootnoteNumbering/EndnoteNumberingを含む)、Section、Document(マクロ有効ドキュメントの生のvbaProject.binバイト列を保持するDocument.VbaProject、Document.Properties、Document.TableStylesを含む)。Xml.fs/Xml.xsd- XML インターフェース:Xml.toDocument/Xml.ofDocumentはDocumentとXElementツリーを相互に変換し、Xml.schemaSet()は、いずれの方向の検証にも使える対応スキーマ(アセンブリ内にリソースとして埋め込み)を読み込みます。以下の "## XML" を参照してください。Json.fs/Json.schema.json- JSON インターフェース:Json.toDocument/Json.ofDocumentはDocumentとSystem.Text.Json.Nodes.JsonObjectツリーを相互に変換します。スキーマ検証はテストスイート内でのみ行われ、パブリック API ではありません。以下の "## JSON" を参照してください。Builders.fs- シンプルな関数型コンストラクター(section、document、withStyles、withNumbering、withProtection、withVbaProject、withDocumentProperties、withTableStyles、bulletListDef、numberedListDef、multiLevelNumberedListDef)とDocumentDsl- 実際の省略可能パラメーターを備えたスマートコンストラクター(run、para(markRevisionを含む)、hyperlink、bookmark、comment、inserted/deleted(変更履歴)、contentControl/contentControlBlock(コンテンツコントロール)、image、footnote、endnote、tableCell、tableRow(height/repeatAsHeaderを含む)、table(style/borders/cellMarginsを含む))。Excel リポジトリのSheetDslに相当します。Interpreter/StyleRegistry.fs- ラン/段落/罫線/色の共通変換と、Document.Styles<->styles.xml(内部)。Interpreter/ImageWriter.fs/ImageReader.fs- インライン画像独自の DSL <-> DrawingML 変換(内部)。Interpreter/Writer.fs- DSL -> OOXML(内部)。Interpreter/Reader.fs- OOXML -> DSL の逆変換(内部)。Interpreter/CodeGen.fs- DSL -> F# ソーステキスト:Documentを、実行すると同等のファイルを再構築する自己完結型の.fsxスクリプトとして出力します(内部)。Api.fs- パブリックなDocument.save/saveToStream/load/loadFromStream/generateScriptエントリーポイント。
src/Kookerella.CsWordDsl- 流れるように記述できる C# ラッパー(以下の "## The C# wrapper" を参照):F# コア独自の型を一対一で反映する、不変レコード/sealed recordによるクローズド階層、DocumentConverter.cs(内部。F#<->C# の双方向変換)、DocumentIO.cs(Save/Load。このプロジェクトで I/O を行う唯一の場所)、CsCodeGen.cs(DSL -> C# ソーステキスト。Interpreter/CodeGen.fsの C# 相当機能)。src/Kookerella.FsWordDsl.Mcp- MCP サーバー(専用の README(src/Kookerella.FsWordDsl.Mcp/README.md)を参照):DocumentTools.fs(ツールのインターフェース。ツールごとに[<McpServerTool>]属性付きメンバーを 1 つ定義)、Program.fs(argvに応じて MCP stdio サーバー、または通常のconvert/buildCLI に処理を振り分けます)。Excel 側の姉妹プロジェクト独自のKookerella.FsOpenXmlDsl.Mcpと同様に、dotnet tool(fsworddsl-mcp)として配布されます。tests/Kookerella.FsWordDsl.Tests- 機能ごとに 1 つのテストがあり、生成したファイルを OOXML スキーマ(DocumentFormat.OpenXml.Validation.OpenXmlValidator)で検証し、DSL を経由して正確に往復変換できることを確認します。各テストは、構築したドキュメントをExamples/<test name>/output.docx(リポジトリにコミット済み)に加えて、script.fsx(ファイルを再生成します。別の低速なCategory=Slowテストグループが、各スクリプトをdotnet fsiで実際に実行します)、document.xml、document.jsonにも書き込みます。常に 1 つのフォルダーに、同じ例の 4 つの形式が格納されます。tests/Kookerella.CsWordDsl.Tests-DriftGuardTests.cs(リフレクションを使った安全装置。C# ラッパーの DU ミラーのケース数が F# コアに遅れを取っていないか確認)、DocumentTests.cs(機能ごとの対象を絞った往復変換のアサーション)、ExampleTests.cs(シナリオをすべて二重に作成せず、F# テストスイートにチェックイン済みのExamples/*/output.docxフィクスチャを再読み込み)、CsCodeGenTests.cs(生成したファイルをdotnet run --fileで実際に実行します。F# テストスイートのCategory=Slowによるdotnet fsiグループの C# 相当機能)。samples/Kookerella.FsWordDsl.Sample- ドキュメントを構築し、保存してから読み戻す、小規模なコンソールアプリ。
クイックスタート
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 は Styles の既定値に BuiltInStyles.all を使用するため、styleId = "Heading1"(またはその他の組み込み ID)は登録しなくてもそのまま使えます。後から withStyles をパイプでつなげば、そのセットを置き換えたり拡張したりできます。run/para/hyperlink/bookmark/comment/image/tableCell/tableRow/table は、実際の省略可能なパラメーターを持つ DocumentDsl のメンバーです(open type Kookerella.FsWordDsl.DocumentDsl を使うと、Excel リポジトリの open type SheetDsl と同じように、修飾なしで使用できるようになります)。通常の F# の let バインディングには省略可能なパラメーターを持たせられないため、この DSL の部分は型になっています。
Paragraph の Inlines は、それぞれ独立したスタイルを適用できる複数のランとして扱われます。リッチテキスト(1 つの段落内で書式を混在させること)は標準機能であり、Excel の単一書式の Text セルのように、ドキュメント化されていない機能ではありません。
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 では、小型大文字、すべて大文字、隠しテキストも指定できます。ParagraphFormat では、境界線(テーブルの境界線と同じ形式の BorderStyle)と網かけを指定できます。
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)) })
カスタムのタブ位置(TabStop)は ParagraphFormat.TabStops に設定します。ドットリーダー付きの右揃えタブは、目次でよく使われる形式です。
para
([ run "Introduction"; Tab; run "1" ], format =
{ ParagraphFormat.Default with TabStops = [ { Position = 288.0; Alignment = RightTab; Leader = DotLeader } ] })
Color では、通常の Rgb/Auto に加えて、テーマに相対的なトークン(Theme)も指定できます。この DSL にはテーマの解決に使うテーマパーツがないため、実際の Word が解決します。Fallback は、テーマのない閲覧環境で表示される色です。これは、Word 自身も採用している「計算済みの値も必ず書き込む」という規則に沿ったものです。
run ("Accent-colored text", style = { RunStyle.Default with Color = Some(Theme(Accent1Theme, (0x1Fuy, 0x49uy, 0x7Duy), None, None)) })
リストでは、段落に (numId, level) の参照を設定し、ドキュメントに関連付けられた NumberingDefinition を使って解決します。NumberingDefinition.Levels は 1 つのレベルに限定されず、multiLevelNumberedListDef を使うと、一般的なアウトライン形式を正しく関連付けて構築できます。
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 ]
テーブルは tableRow/tableCell から構築し、列幅はテーブル全体に対して一度だけ指定します。明示的な幅が設定されていないセルには、書き込み時にその列の幅が適用されます。
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
)
セルの結合には、水平方向(GridSpan)と垂直方向(RestartMerge/ContinueMerge)があり、それぞれ独立しているため、実際の Word と同様に同じセルで組み合わせられます。
tableCell ([ para [ run "Spans 2 columns" ] ], props = { TableCellProps.Default with GridSpan = Some 2 })
セル独自の余白を設定すると、テーブルの既定値より優先されます。どちらも同じ形式の CellMargins で指定します。
tableCell ([ para [ run "Extra padding" ] ], props = { TableCellProps.Default with Margins = Some { CellMargins.Default with Top = Some 8.0; Bottom = Some 8.0 } })
カスタムのテーブルスタイル(TableStyleDefinition)は Document.TableStyles に格納し、組み込みスタイルの "TableGrid" と同じように名前で適用します。ここでは、青い背景に白の太字を設定したヘッダー行、斜体の最終行、交互の行の網かけに加え、テーブル全体の既定のセル余白と、各ページで繰り返される行を設定しています。
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 では、FirstColumn/LastColumn、BandedColumn、4 つの角のセル(NorthEastCell/NorthWestCell/SouthEastCell/SouthWestCell)も指定できます。モデル化されていないのは、各縞模様の軸における 2 つ目の帯の 2 領域です。実際には、これらには WholeTable 自体の背景がそのまま表示されるためです(MAPPING.md を参照)。
各セクションには独自のページ設定があります。ドキュメントは Section の並びであり、実際の Word のセクション区切りと 1 対 1 で対応します。BreakType は、セクションが前のセクションに対してどのように開始するかを指定します。最初のセクションでは意味を持たず、書き込まれません。
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." ] ] ]
脚注と文末脚注は、段落自身の Inlines 内の位置に付けます。content は注記自体の本文で、Writer が ID を自動的に割り当てて word/footnotes.xml/endnotes.xml に書き込まれます。参照記号のランは、開始側と終了側の両方で自動生成されます。
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." ] ] ]
セクション独自の脚注/文末脚注の番号設定(w:footnotePr/w:endnotePr)です。None は Word 自身の既定値(1 から始まる連続した 10 進数)を意味します。ここでは、一般的な法律文書の慣例に合わせて、脚注を小文字のローマ数字にし、ページごとに番号を振り直しています。
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." ] ]
ヘッダーとフッターはセクションごとに設定し、Default/First/Even のバリエーションを指定できます(実際の Word で必要な titlePg/evenAndOddHeaders フラグは自動的に設定されます)。
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." ] ]
コメントとブックマークは、よく使われる単一段落のケースでは、インラインコンテンツを直接囲みます。
para [ comment ([ run "This figure needs review." ], "Please double check the totals.", author = "Alex") ]
複数の段落にまたがる場合は、代わりに個別の段落に直接配置した2つの独立したマーカーを使い、同じ ID を共有します。ブックマークには BookmarkRangeStart/BookmarkRangeEnd、コメントには CommentRangeStart/CommentRangeEnd を使います(ここでは囲むケースがないため、コメント自体のメタデータは Start に保持されます。この ID がブックマーク名とは異なり書き込み時のみ使われる理由については、MAPPING.md を参照してください)。
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" ] ] ]
変更履歴(inserted/deleted)では、インラインコンテンツを同じ方法で囲み、作成者と日付を記録して挿入または削除としてマークします。段落全体を挿入または削除する場合(コンテンツの一部だけではない場合)は、段落の終了マークに対して para 自体の markRevision を使います。
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 })
コンテンツコントロール(ランレベルでは contentControl、ブロックレベルでは contentControlBlock)は、現在表示されているコンテンツ自体を同じ方法で囲み、さらに ContentControlType を指定します(種類の一覧については MAPPING.md を参照してください。プレーンテキスト、リッチテキスト、ドロップダウン/コンボボックス、日付ピッカー、チェックボックスがあります)。
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")
ドキュメントレベルの保護、マクロ、コアプロパティはすべてパイプに対応しており、Excel 自身の 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" }
結果は .docm パスで保存してください。VbaProject が存在すると、Document.save/saveToStream はファイル自体に宣言されたコンテンツタイプを Word のマクロ有効形式に自動で切り替えますが、マクロを信頼して実行するには、実際の Word では .docm 拡張子も必要です。
F# ソースとしてファイルを再生成する
Document(通常は既存ファイルから Document.load したもの)があれば、Document.generateScript によって、実行すると同等のファイルを再構築する自己完結型の .fsx スクリプトとして出力できます。これは 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)
dotnet fsi regenerate.fsx を実行すると output.docx が生成されます。元のファイルとバイト単位で同一にはなりません(ZIP のメタデータやタイムスタンプが異なります)が、このリポジトリの他のすべてのテストで使われている同じラウンドトリップの観点では、構造的に同等です。tests/Kookerella.FsWordDsl.Tests/Examples/ の各シナリオには、この方法で生成された script.fsx がコミットされています。Category=Slow のテストグループでは、各スクリプトを dotnet fsi で実際に実行し、コミット済みの .docx を再現できることを確認します。
C# ラッパー
Kookerella.CsWordDsl は、F# の判別共用体やオプション型を直接扱いたくない呼び出し元向けに、F# コアを慣用的なイミュータブルで流暢な C# でラップしたものです。すべての F# 型に C# のミラー型があります。プロダクト型には With*/ファクトリメソッドビルダーを備えたプレーンなレコード、パラメーターなしの選択肢には enum、それ以外には sealed record の閉じた階層(abstract record 基底型、プライベートコンストラクター、ネストされたケース)を使用しています。これは、Excel リポジトリ独自の Kookerella.CsOpenXmlDsl が CellValue/ConditionalFormatRule に使用しているものと同じ「シールド階層」パターンです。Kookerella.FsWordDsl ではなく Kookerella.CsWordDsl を参照すれば、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");
コンテンツコントロール、テーブル、変更履歴、コメントなど、F# コアがモデル化しているすべての機能が同じ方法でカバーされています。各機能の具体例については、tests/Kookerella.CsWordDsl.Tests/DocumentTests.cs をご覧ください。CsCodeGen.Generate は Document.generateScript に相当する C# 版で、Document を、.fsx スクリプトではなく、.NET の「ファイルベースアプリ」機能(dotnet run --file script.cs)を対象とした自己完結型の C# ファイルとして出力します。
var script = CsCodeGen.Generate(["#:project path/to/Kookerella.CsWordDsl.csproj"], "output.docx", loaded);
File.WriteAllText("regenerate.cs", script);
DocumentIO には、F# コアのほかの入出力方法も直接用意されています。スキーマに基づく XML/JSON(ToXml/FromXml、ToJson/FromJson)と F# スクリプト生成(GenerateFSharpScript。F# を対象とする CsCodeGen.Generate の兄弟機能)です。そのため、C# の呼び出し元は、C# から F# コアの 4 つの入出力方法を利用するために、Kookerella.FsWordDsl を別途参照する必要はありません。
明記しておくべき設計上の注意点が 1 つあります。このラッパーのレコードは IReadOnlyList<T> プロパティを使用していますが、C# コンパイラーが合成するレコードの等価性判定ではリストの内容を深く比較しません(同じ内容で別のインスタンスを保持する 2 つのレコードは、通常の .Equals() では等しくないと判定されます)。これは Kookerella.CsOpenXmlDsl 独自のレコードにも当てはまる制限です。コード内で Document 全体の等価性に依存しないでください。代わりに、このリポジトリ独自の DocumentTests.cs と同じように、必要な値を個別に比較してください。
XML
Xml.fs にある Xml.toDocument/Xml.ofDocument は、DSL の入出力を行う 3 つ目の方法です。F# を直接記述する方法やコード生成に加えて、実際のスキーマ(アセンブリに埋め込まれた Xml.xsd)に準拠したプレーンな XML を使用できます。データを持つ DU ケースはケース名を要素名として表現され、パラメーターなしの選択肢のケースは属性値または単独の文字列として表現されます。Excel リポジトリ独自の Xml.fs に記載されている規約と同じです。
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"
直接書式設定とハイパーリンクを含む実行結果の 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() はコンパイル済みのスキーマを読み込み、どちらの方向でも自分で検証できるようにします(XDocument.Validate)。tests/Kookerella.FsWordDsl.Tests/Examples/ 以下の各シナリオには、コミット済みの document.xml があり、生成と同じテストの一部としてこの方法で検証されています。
toDocument の出力は決定的な順序になります(基になる Document 自体のリストの順序にかかわらず、Styles/Numbering/TableStyles は Id 順にソートされます)。段落/実行の内容はすでに実際の文書順になっているためソートは不要ですが、これら 3 つは ID で参照されるカタログであり、カタログ自体のリスト順に意味はありません。そのため、document.xml をバージョン管理にコミットしてコミット間で差分を比較することに実質的な意味が生まれます。カタログが並べ替えられただけの、ほかは同一の 2 つの文書間で無関係な差分が生じるのではなく、実際の内容変更だけが小さく限定された差分として現れます。
JSON
Json.fs の Json.toDocument/Json.ofDocument は、DSL との変換に使える 4 つ目の方法です。F# を直接記述する方法、コード生成、XML に加えて、XML ではなく JSON を扱うツールを使う呼び出し元向けに、プレーンな JSON を利用できます。JSON 独自の形式で、DU ケースに関する同じ規則が適用されます(データを持つケースは単一キーのオブジェクト、パラメーターを持たないケースは単独の文字列です)。
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())
上記と同じハイパーリンクの例を、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." } }
]
}
}
Xml.toDocument と同じ決定性(Styles/Numbering/TableStyles を Id でソート)が、Json.toDocument の出力にも適用されます。同じ理由から、実際にコンテンツを変更した場合は、ほかの部分が同一のドキュメント間でカタログの順序が入れ替わって生じる不要な差分ではなく、小さく独立した差分になります。
XML とは異なり、.NET には組み込みの JSON Schema バリデーターがないため、Json.schema.json(リポジトリ内)の検証は公開 API として提供されず、このリポジトリ独自のテストスイートからのみ(テスト専用の JsonSchema.Net 依存関係を介して)行われます。詳しくは Json.fs 自身のドキュメントコメントを参照してください。
ビルドとテスト
dotnet build
dotnet test --filter "Category!=Slow"
dotnet run --project samples/Kookerella.FsWordDsl.Sample
上記のデフォルトのテストループでは、時間のかかる Category=Slow テストはスキップされます。このテストでは、生成された Examples/*/script.fsx ごとに実際に dotnet fsi を呼び出します(プロセスの起動だけで毎回数秒かかります)。低速テストは、少なくとも一度高速テストを実行して .fsx ファイルを生成してから、明示的に実行してください。
dotnet test --filter "Category=Slow"
フィルターなしの通常の dotnet test では、両方のテストグループが実行されます。
C# ラッパー独自のテストスイートには、高速/低速の区分がありません。CsCodeGenTests.cs がそれ自体で dotnet run --file をシェルから実行するため、1 回の実行で F# テストスイートの低速テストに相当する C# のテストもすでに網羅できます。
dotnet test tests/Kookerella.CsWordDsl.Tests
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。