<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ilia Sokolov</title>
    <description>The latest articles on DEV Community by Ilia Sokolov (@ilia-sokolov).</description>
    <link>https://dev.to/ilia-sokolov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3946653%2F670f803c-9cbe-45f6-87da-a9bf8931589b.jpeg</url>
      <title>DEV Community: Ilia Sokolov</title>
      <link>https://dev.to/ilia-sokolov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ilia-sokolov"/>
    <language>en</language>
    <item>
      <title>Add Word-Editing Tools to a .NET IChatClient with OfficeAgent.NET</title>
      <dc:creator>Ilia Sokolov</dc:creator>
      <pubDate>Sat, 01 Aug 2026 13:19:22 +0000</pubDate>
      <link>https://dev.to/ilia-sokolov/add-word-editing-tools-to-a-net-ichatclient-with-officeagentnet-2e76</link>
      <guid>https://dev.to/ilia-sokolov/add-word-editing-tools-to-a-net-ichatclient-with-officeagentnet-2e76</guid>
      <description>&lt;p&gt;Most AI demos end with text in a chat window. Business workflows often need something more concrete: a Word document that a person can open, review, and approve.&lt;/p&gt;

&lt;p&gt;If your .NET application already uses &lt;em&gt;Microsoft.Extensions.AI.IChatClient&lt;/em&gt;, you do not need to adopt a full agent framework to add that capability. &lt;a href="https://github.com/ilia-sokolov/OfficeAgent.NET" rel="noopener noreferrer"&gt;OfficeAgent.NET&lt;/a&gt; can expose bounded Word-editing operations directly as &lt;em&gt;AIFunction&lt;/em&gt; tools.&lt;/p&gt;

&lt;p&gt;The integration has a clear boundary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;IChatClient&lt;/em&gt; manages the conversation and function-calling loop.&lt;/li&gt;
&lt;li&gt;OfficeAgent.NET inspects, plans, previews, and applies Word changes.&lt;/li&gt;
&lt;li&gt;Your application controls document storage and delivery.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The document bytes stay outside the model context.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow
&lt;/h2&gt;

&lt;p&gt;The host performs five steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register a document and receive an opaque document id.&lt;/li&gt;
&lt;li&gt;Expose OfficeAgent.NET operations as AI functions.&lt;/li&gt;
&lt;li&gt;Add those functions to the existing &lt;em&gt;IChatClient&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Capture the output document id returned by &lt;em&gt;apply_plan&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Retrieve the finished &lt;em&gt;.docx&lt;/em&gt; through the configured document provider.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The complete sample makes one tracked edit: it changes &lt;em&gt;60 days&lt;/em&gt; to &lt;em&gt;30 days&lt;/em&gt; without modifying anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Register the document and create the tools
&lt;/h2&gt;

&lt;p&gt;Install the OfficeAgent.NET packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package OfficeAgent.Core &lt;span class="nt"&gt;--version&lt;/span&gt; 0.2.1
dotnet add package OfficeAgent.Word &lt;span class="nt"&gt;--version&lt;/span&gt; 0.2.1
dotnet add package OfficeAgent.AgentFramework &lt;span class="nt"&gt;--version&lt;/span&gt; 0.2.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then configure Word support and register the source document:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;ServiceProvider&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ServiceCollection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWordFormat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddFileSystemDocumentProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"contracts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storageRoot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOfficeAgent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildServiceProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;OfficeAgentClient&lt;/span&gt; &lt;span class="n"&gt;office&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OfficeAgentClient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="n"&gt;DocumentReference&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;office&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"contracts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storageRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"contract.docx"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="n"&gt;AIFunction&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OfficeAgentTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;office&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsAIFunctions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Despite the package name, this path does not use &lt;em&gt;ChatClientAgent&lt;/em&gt;.&lt;br&gt;
&lt;em&gt;AsAIFunctions()&lt;/em&gt; returns ordinary functions that can be attached to an &lt;em&gt;IChatClient&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Add function execution to IChatClient
&lt;/h2&gt;

&lt;p&gt;Create the Azure OpenAI client, convert it to &lt;em&gt;IChatClient&lt;/em&gt;, and add function invocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;IChatClient&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AzureOpenAIClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetChatClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deployment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsIChatClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseFunctionInvocation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;UseFunctionInvocation()&lt;/em&gt; runs the tool calls requested by the model, returns their structured results, and continues until the model produces its final response.&lt;/p&gt;

&lt;p&gt;In a production host, add a request-scoped function invoker that records tool calls and captures &lt;em&gt;outputDocumentId&lt;/em&gt; from a successful &lt;em&gt;apply_plan&lt;/em&gt; result. Do not rely on the model to repeat that id in its prose, and do not store request state in a shared singleton closure.&lt;/p&gt;

&lt;p&gt;The runnable sample includes result normalization because function adapters can surface the same JSON result as either an object or a JSON string. Handling both forms makes output capture reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Give the model a bounded editing request
&lt;/h2&gt;

&lt;p&gt;Provide the registered document id, OfficeAgent guidance, and a narrow instruction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ChatRole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;$"""
&lt;/span&gt;        &lt;span class="n"&gt;You&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;editing&lt;/span&gt; &lt;span class="n"&gt;connectionId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="n"&gt;contracts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;={&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ItemId&lt;/span&gt;&lt;span class="p"&gt;}.&lt;/span&gt;

        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;OfficeAgentTools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SystemPromptGuidance&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;Inspect&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="n"&gt;planning&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Preview&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="n"&gt;applying&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;When&lt;/span&gt; &lt;span class="n"&gt;applying&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="n"&gt;saveMode&lt;/span&gt; &lt;span class="n"&gt;NewVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="s"&gt;"""),
&lt;/span&gt;    &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ChatRole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Change '60 days' to '30 days' as a tracked change. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
        &lt;span class="s"&gt;"Make no other edits."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ChatOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Tools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cast&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AITool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;AllowMultipleToolCalls&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MaxOutputTokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1200&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;ChatResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetResponseAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The safety sequence is discovery, preview, then apply. OfficeAgent.NET validates the edit plan before changing the document, while the host retains control of which document is registered and where the result is stored.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Retrieve the edited Word document
&lt;/h2&gt;

&lt;p&gt;After &lt;em&gt;apply_plan&lt;/em&gt; commits successfully, retrieve the document using the output id captured by the host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;office&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenReadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;DocumentReference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ForFileSystem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"contracts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;outputDocumentId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"reviewed-contract.docx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CopyToAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model never needs the &lt;em&gt;.docx&lt;/em&gt; bytes. Your application can return the file through its normal download, attachment, SharePoint, or storage workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the complete sample
&lt;/h2&gt;

&lt;p&gt;The article excerpts focus on the integration boundary. The&lt;br&gt;
&lt;a href="https://github.com/ilia-sokolov/OfficeAgent.NET/tree/be48292dd6cf1f3b764063ecdd7f301cb429b98e/samples/IChatClientWordEdit" rel="noopener noreferrer"&gt;complete runnable sample&lt;/a&gt; contains the full host, package references, Azure configuration, JSON result normalization, synthetic fixture, logging, and output checks.&lt;/p&gt;

&lt;p&gt;Start with a synthetic document, keep the first run single-request, and confirm that the trace ends with &lt;em&gt;apply_plan&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;OfficeAgent.NET gives an existing .NET chat application a focused way to produce reviewable Word documents without moving file content through the model or replacing the application's current AI architecture.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>dotnet</category>
      <category>showdev</category>
      <category>agents</category>
    </item>
    <item>
      <title>How a Word-editing MCP server can reduce agent output tokens</title>
      <dc:creator>Ilia Sokolov</dc:creator>
      <pubDate>Sat, 01 Aug 2026 12:49:46 +0000</pubDate>
      <link>https://dev.to/ilia-sokolov/how-a-word-editing-mcp-server-can-reduce-agent-output-tokens-40ha</link>
      <guid>https://dev.to/ilia-sokolov/how-a-word-editing-mcp-server-can-reduce-agent-output-tokens-40ha</guid>
      <description>&lt;p&gt;Codex and Claude can edit Word documents without a dedicated document tool. They can open a &lt;code&gt;.docx&lt;/code&gt; as a ZIP package, inspect its XML, write Python or C# to find the right node, change it, and save the package again.&lt;/p&gt;

&lt;p&gt;That works, but the agent has to produce the editing toolset as well as the edit. Even a small request can turn into code for locating text across Word runs, preserving formatting, handling tracked changes, and keeping the document valid.&lt;/p&gt;

&lt;p&gt;I wanted to know whether a Model Context Protocol (MCP) server that already implements that tools could reduce the output tokens an agent emits. I used &lt;a href="https://github.com/ilia-sokolov/OfficeAgent.NET" rel="noopener noreferrer"&gt;OfficeAgent.NET&lt;/a&gt;, which exposes Word operations such as find, replace, format, comment, and accept revisions as tools. Instead of writing the XML manipulation, the agent can describe the change in a compact, structured tool call.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I tested
&lt;/h2&gt;

&lt;p&gt;Four agent and model combinations edited the same Word contract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code with Sonnet 5;&lt;/li&gt;
&lt;li&gt;Claude Code with Opus 4.8;&lt;/li&gt;
&lt;li&gt;Codex with GPT 5.6 Sol;&lt;/li&gt;
&lt;li&gt;Codex with GPT 5.6 Terra.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each received the same seven editing tasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;change text with tracked revisions;&lt;/li&gt;
&lt;li&gt;format a paragraph;&lt;/li&gt;
&lt;li&gt;fill a content control;&lt;/li&gt;
&lt;li&gt;insert a sentence;&lt;/li&gt;
&lt;li&gt;insert a table;&lt;/li&gt;
&lt;li&gt;add a review comment;&lt;/li&gt;
&lt;li&gt;accept all revisions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The benchmark compared two paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Direct editing:&lt;/strong&gt; the agent used the Open XML SDK or raw OOXML, with no document MCP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP editing:&lt;/strong&gt; the agent performed the edit through OfficeAgent.NET.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The primary comparisons ran five attempts per task and condition. A separate verifier, built directly on the Open XML SDK, checked the saved document. The token figures below use medians from verifier-successful attempts.&lt;/p&gt;

&lt;p&gt;This benchmark covers edits to an existing document only. Creating a document from scratch is a different workload and is not included.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvmlmdlm6oiuyj9ag3g2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvmlmdlm6oiuyj9ag3g2w.png" alt="MCP output tokens relative to direct editing" width="799" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Across the seven tasks, the median task-level MCP/direct output-token ratios were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code, Sonnet 5:&lt;/strong&gt; 33%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code, Opus 4.8:&lt;/strong&gt; 28%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex, GPT 5.6 Sol:&lt;/strong&gt; 36%&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex, GPT 5.6 Terra:&lt;/strong&gt; 51%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lower is better. A result of 33%, for example, means that the MCP workflow used about one third of the output tokens of direct editing for the median task-level comparison.&lt;/p&gt;

&lt;p&gt;Every one of the 28 task and model comparisons had a lower successful-attempt median when the MCP path was used.&lt;/p&gt;

&lt;p&gt;The reduction was not identical for every operation. Accepting revisions and adding comments benefited more than some simpler formatting and insertion tasks.&lt;/p&gt;

&lt;p&gt;That supports the proposed mechanism: the more document-specific procedure the tool absorbs, the less implementation code the model has to generate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Token savings and cost savings are not the same thing
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F770aepw43ck9dpt30itt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F770aepw43ck9dpt30itt.png" alt="Claude session cost relative to direct editing" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The output-token reduction was large. The session-cost reduction was smaller.&lt;/p&gt;

&lt;p&gt;For Claude, the median task-level session-cost ratio was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;83% of direct on Sonnet&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;84% of direct on Opus&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two of the fourteen Claude task and model comparisons cost slightly more with MCP, even though they used fewer output tokens.&lt;/p&gt;

&lt;p&gt;A plausible contributor is input overhead. MCP tool definitions add context, while fresh input, cache reads, cache writes, and output tokens are priced differently. This benchmark did not isolate the contribution of each component.&lt;/p&gt;

&lt;p&gt;The recorded figures are model-session costs, not total cost of ownership. They do not include the cost of hosting, securing, or maintaining the MCP server. Codex did not report comparable USD costs, so I did not make a cross-agent dollar comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this result does not prove
&lt;/h2&gt;

&lt;p&gt;This was a pilot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one Word document;&lt;/li&gt;
&lt;li&gt;seven supported editing operations;&lt;/li&gt;
&lt;li&gt;two agent environments;&lt;/li&gt;
&lt;li&gt;two models per agent;&lt;/li&gt;
&lt;li&gt;five attempts per primary comparison cell.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The direct baseline had the Open XML SDK available, but it did not have a mature reusable Word-editing abstraction. A team that already owns such an abstraction may see a smaller difference.&lt;/p&gt;

&lt;p&gt;The verifier checked whether the requested OOXML change was present. It did not prove full visual equivalence or rule out every unintended change elsewhere in the document.&lt;/p&gt;

&lt;p&gt;The result also does not mean that every MCP server will reduce tokens. The operation must be supported, and the agent must actually use the tool path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In some Word-editing workflows, an MCP server can let agents emit fewer output tokens by moving document-specific implementation out of the conversation and into a reusable tool.&lt;/p&gt;

&lt;p&gt;In this pilot, the median task-level MCP/direct ratio ranged from 28% to 51%, depending on the agent and model.&lt;/p&gt;

&lt;p&gt;The practical lesson is not that MCP automatically makes agents cheaper. It is that a well-matched tool can remove a large amount of procedural work from the model.&lt;/p&gt;

&lt;p&gt;For teams using AI agents to edit Word documents, that is worth testing against the operations they perform most often.&lt;/p&gt;

&lt;p&gt;The project used in this benchmark is available on GitHub: &lt;a href="https://github.com/ilia-sokolov/OfficeAgent.NET" rel="noopener noreferrer"&gt;OfficeAgent.NET&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>azure</category>
      <category>claude</category>
      <category>openai</category>
    </item>
    <item>
      <title>Letting a Copilot Studio Agent Edit Word Documents in SharePoint</title>
      <dc:creator>Ilia Sokolov</dc:creator>
      <pubDate>Sat, 01 Aug 2026 12:37:50 +0000</pubDate>
      <link>https://dev.to/ilia-sokolov/letting-a-copilot-studio-agent-edit-word-documents-in-sharepoint-306n</link>
      <guid>https://dev.to/ilia-sokolov/letting-a-copilot-studio-agent-edit-word-documents-in-sharepoint-306n</guid>
      <description>&lt;p&gt;Copilot Studio makes it easy to build custom AI agents. In very little time you can create, for example, an agent that drafts a standard customer contract from a few details. But it gets harder when the task is more than filling in blanks. Choosing different clauses for different clients, or editing an existing document instead of creating a new one, is not easy without additional tools. That's where &lt;a href="https://github.com/ilia-sokolov/OfficeAgent.NET" rel="noopener noreferrer"&gt;OfficeAgent&lt;/a&gt; helps. As an MCP server, it gives agents tools to edit real Word documents, keeping their styling, comments, and tracked changes intact.&lt;/p&gt;

&lt;p&gt;In the previous article I explained &lt;a href="https://dev.to/ilia-sokolov/deploying-the-officeagentnet-mcp-server-to-azure-68d/"&gt;how to deploy the OfficeAgent MCP server to Azure&lt;/a&gt;. This article connects a Copilot Studio agent to it and lets it modify an existing Word document.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you need before you start
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. The hosted MCP endpoint&lt;/strong&gt; from the previous article. From that deployment you will need your &lt;strong&gt;tenant id&lt;/strong&gt; and the &lt;strong&gt;Easy Auth app's client id&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A Copilot Studio agent&lt;/strong&gt; - an existing one or a new blank agent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. An app registration for Copilot Studio to sign in with.&lt;/strong&gt; Copilot Studio needs its own identity to obtain an Entra token to connect to the endpoint:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the &lt;a href="https://entra.microsoft.com" rel="noopener noreferrer"&gt;Entra admin center&lt;/a&gt;, open &lt;strong&gt;App
registrations&lt;/strong&gt; and create a new registration. Name it (e.g.
&lt;code&gt;officeagent-copilot&lt;/code&gt;), keep the defaults, and register it. Copy the
&lt;strong&gt;Application (client) ID&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On the app's &lt;strong&gt;Certificates &amp;amp; secrets&lt;/strong&gt; page, create a new client secret
and copy its &lt;strong&gt;Value&lt;/strong&gt; right away - it is shown only once.&lt;/li&gt;
&lt;li&gt;You will add a redirect URL to this registration later - Copilot Studio
shows it during the connection setup.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Add the MCP server to the agent
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open your agent in Copilot Studio and check that it uses &lt;strong&gt;generative
orchestration&lt;/strong&gt; (on the agent's &lt;strong&gt;Settings&lt;/strong&gt; page, under &lt;strong&gt;Generative
AI&lt;/strong&gt;). MCP tools are not available under classic orchestration.&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;Tools&lt;/strong&gt; page, click &lt;strong&gt;Add a tool&lt;/strong&gt;, and pick &lt;strong&gt;Model Context
Protocol&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enter a name (&lt;code&gt;OfficeAgent&lt;/code&gt;), a short description ("edits Word documents
in SharePoint through validated change plans"), and the &lt;strong&gt;server URL&lt;/strong&gt;:
&lt;code&gt;https://&amp;lt;app-url&amp;gt;/&lt;/code&gt;, the root of the deployed container app.&lt;/li&gt;
&lt;li&gt;For authentication, pick &lt;strong&gt;OAuth 2.0&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The OAuth values
&lt;/h3&gt;

&lt;p&gt;All of these come from the deployment:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client ID&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the &lt;code&gt;officeagent-copilot&lt;/code&gt; app's client id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Client secret&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the secret you created for it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Authorization URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://login.microsoftonline.com/&amp;lt;tenant-id&amp;gt;/oauth2/v2.0/authorize&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Token URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://login.microsoftonline.com/&amp;lt;tenant-id&amp;gt;/oauth2/v2.0/token&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Refresh URL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;same as the token URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scopes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the scope the Easy Auth registration exposes, on its &lt;strong&gt;Expose an API&lt;/strong&gt; page, typically &lt;code&gt;api://&amp;lt;easyauth-client-id&amp;gt;/user_impersonation&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Finish the connection
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Finish the wizard. Copilot Studio now shows the &lt;strong&gt;redirect URL&lt;/strong&gt; for this
connection - copy it, go back to the &lt;code&gt;officeagent-copilot&lt;/code&gt; registration in
the Entra admin center, and add it under &lt;strong&gt;Authentication&lt;/strong&gt; as a &lt;strong&gt;Web&lt;/strong&gt;
redirect URI.&lt;/li&gt;
&lt;li&gt;Create the connection when Copilot Studio asks you to sign in. This is
the moment the OAuth loop closes: you sign in, consent, and the tool
shows as connected.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the tool listing you should see seven tools: &lt;code&gt;inspect_document&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;find_in_document&lt;/code&gt;, &lt;code&gt;preview_plan&lt;/code&gt;, &lt;code&gt;apply_plan&lt;/code&gt;, &lt;code&gt;register_document&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;remove_document&lt;/code&gt;, and &lt;code&gt;list_connections&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The first edit
&lt;/h3&gt;

&lt;p&gt;With the tools added, you can ask the agent something concrete:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Update the payment terms in&lt;br&gt;
&lt;a href="https://contoso.sharepoint.com/sites/legal/Shared%20Documents/msa-draft.docx" rel="noopener noreferrer"&gt;https://contoso.sharepoint.com/sites/legal/Shared%20Documents/msa-draft.docx&lt;/a&gt;&lt;br&gt;
from 30 to 45 days.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Under the hood, the agent will register the document with the OfficeAgent MCP server, find all appearances of "30 days", prepare a modification plan, and apply it.&lt;/p&gt;

&lt;p&gt;The result lands in SharePoint as a new version of the document, and the edit itself is a &lt;strong&gt;Word tracked change&lt;/strong&gt; - open the document and you review it like a colleague's edit, accept or reject. The agent proposes the change; you decide what the document finally says.&lt;/p&gt;

&lt;h3&gt;
  
  
  Whose permissions apply
&lt;/h3&gt;

&lt;p&gt;In the deployment article's setup the server uses one application identity (&lt;code&gt;appOnly&lt;/code&gt;), scoped with &lt;code&gt;Sites.Selected&lt;/code&gt; to the sites you granted. Every user of the agent reaches the same documents, which fits back-office automation. The provider also supports an &lt;strong&gt;on-behalf-of&lt;/strong&gt; mode, where the server acts as the signed-in user and SharePoint permissions apply per user - the right choice for an assistant many people share. The identity table in the &lt;a href="https://github.com/ilia-sokolov/OfficeAgent.NET/blob/main/docs/deployment.md" rel="noopener noreferrer"&gt;deployment guide&lt;/a&gt;&lt;br&gt;
covers what that mode requires.&lt;/p&gt;

&lt;p&gt;Let me know in the comments if this article is useful, and a star on &lt;a href="https://github.com/ilia-sokolov/OfficeAgent.NET" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; helps other developers find it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>dotnet</category>
      <category>azure</category>
      <category>sharepoint</category>
    </item>
    <item>
      <title>Deploying the OfficeAgent.NET MCP Server to Azure</title>
      <dc:creator>Ilia Sokolov</dc:creator>
      <pubDate>Sat, 01 Aug 2026 12:19:18 +0000</pubDate>
      <link>https://dev.to/ilia-sokolov/deploying-the-officeagentnet-mcp-server-to-azure-68d</link>
      <guid>https://dev.to/ilia-sokolov/deploying-the-officeagentnet-mcp-server-to-azure-68d</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/ilia-sokolov/OfficeAgent.NET" rel="noopener noreferrer"&gt;OfficeAgent.NET&lt;/a&gt; ships an MCP server that gives AI agents tools to edit real Word documents through typed, validated change plans. You can start it locally with a few commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; OfficeAgent.Mcp
officeagent-mcp &lt;span class="nt"&gt;--stdio&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is perfect if you want to use it with locally running agents such as Claude Code or Codex. But local is not always enough. Hosting becomes necessary in these cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hosted agents.&lt;/strong&gt; Copilot Studio and Microsoft 365 Copilot can only
consume MCP over streamable HTTP at a public HTTPS endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unattended automation.&lt;/strong&gt; A document workflow that runs on schedule or on
events needs the server running somewhere that isn't a laptop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared server for a team.&lt;/strong&gt; Instead of every user holding SharePoint
credentials in a local config, one hosted instance holds them once, and
clients get a URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article shows how to deploy that endpoint to Azure Container Apps, connect it to your SharePoint document storage, and secure it with built-in authentication (Easy Auth).&lt;/p&gt;

&lt;h3&gt;
  
  
  What you need before you start
&lt;/h3&gt;

&lt;p&gt;Before you start the deployment you need to prepare the following:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The container image.&lt;/strong&gt; This article uses a prebuilt image available on the GitHub Container Registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ghcr.io/ilia-sokolov/officeagent-mcp:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feel free to use it. If your policy requires your own registry, build the image from the Dockerfile in the repository, push it to your registry, and replace the reference - the steps are the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Your tenant id.&lt;/strong&gt; In the&lt;br&gt;
&lt;a href="https://entra.microsoft.com" rel="noopener noreferrer"&gt;Entra admin center&lt;/a&gt;, it's on the &lt;strong&gt;Overview&lt;/strong&gt; page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. An app registration for SharePoint access.&lt;/strong&gt; OfficeAgent works through&lt;br&gt;
providers: you define which document storage the agents may reach. It ships a filesystem provider and a SharePoint provider; for a hosted server,&lt;br&gt;
SharePoint is the best option. The server needs its own identity to reach the documents:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the Entra admin center, open &lt;strong&gt;App registrations&lt;/strong&gt; and create a new
registration. Name it (e.g. &lt;code&gt;officeagent-sharepoint&lt;/code&gt;), keep the defaults,
and register it. Copy the &lt;strong&gt;Application (client) ID&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On the app's &lt;strong&gt;Certificates &amp;amp; secrets&lt;/strong&gt; page, create a new client secret.
Copy its &lt;strong&gt;Value&lt;/strong&gt; right away - it is shown only once.&lt;/li&gt;
&lt;li&gt;Under &lt;strong&gt;API permissions&lt;/strong&gt;, add a permission: pick &lt;strong&gt;Microsoft Graph&lt;/strong&gt;,
then &lt;strong&gt;Application permissions&lt;/strong&gt;. Add &lt;strong&gt;&lt;code&gt;Sites.Selected&lt;/code&gt;&lt;/strong&gt; (least
privilege - the app reaches only sites you explicitly grant) or
&lt;strong&gt;&lt;code&gt;Sites.ReadWrite.All&lt;/code&gt;&lt;/strong&gt; (simpler, but every site in the tenant). Then
grant admin consent.&lt;/li&gt;
&lt;li&gt;If you chose &lt;code&gt;Sites.Selected&lt;/code&gt;, grant the app &lt;code&gt;Write&lt;/code&gt; access to the specific site or sites, for example with PnP PowerShell:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Grant-PnPAzureADAppSitePermission&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-AppId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;app-client-id&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-DisplayName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;app-name&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Site&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;site-url&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;-Permissions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploy MCP in Azure portal
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Create the app:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the Azure portal, create a new &lt;strong&gt;Container App&lt;/strong&gt; resource. Pick a
resource group, name the app (e.g. &lt;code&gt;officeagent-mcp&lt;/code&gt;), and let it create
a new Container Apps environment.&lt;/li&gt;
&lt;li&gt;On the &lt;strong&gt;Container&lt;/strong&gt; tab: uncheck &lt;strong&gt;Use quickstart image&lt;/strong&gt;. Name the
container (e.g. &lt;code&gt;officeagent-mcp&lt;/code&gt;), set &lt;strong&gt;Image source&lt;/strong&gt; to &lt;em&gt;Docker Hub or
other registries&lt;/em&gt;, &lt;strong&gt;Image type&lt;/strong&gt; to &lt;em&gt;Public&lt;/em&gt;, set &lt;strong&gt;Registry login
server&lt;/strong&gt; to &lt;code&gt;ghcr.io&lt;/code&gt;, and set &lt;strong&gt;Image and
tag&lt;/strong&gt; to &lt;code&gt;ilia-sokolov/officeagent-mcp:latest&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Environment variables&lt;/strong&gt; grid is at the bottom of the same tab. Add
the six variables from the table below.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These values configure the server's SharePoint connection:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OfficeAgent__SharePointConnections__0__ConnectionId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;documents&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OfficeAgent__SharePointConnections__0__AuthMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;appOnly&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OfficeAgent__SharePointConnections__0__TenantId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;your tenant id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OfficeAgent__SharePointConnections__0__ClientId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the SharePoint app's client id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OfficeAgent__SharePointConnections__0__ClientSecret&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the SharePoint app's client secret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;OfficeAgent__SharePointConnections__0__RegistrationIndexPath&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/tmp/officeagent-index.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In this article we set up &lt;code&gt;appOnly&lt;/code&gt;: the server acts as one application&lt;br&gt;
identity for every caller. The provider also supports an &lt;strong&gt;on-behalf-of&lt;/strong&gt;&lt;br&gt;
flow, where the server acts as the signed-in user and SharePoint permissions&lt;br&gt;
apply per user. See the&lt;br&gt;
&lt;a href="https://github.com/ilia-sokolov/OfficeAgent.NET/blob/main/docs/deployment.md" rel="noopener noreferrer"&gt;deployment guide&lt;/a&gt;&lt;br&gt;
if you need that.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the &lt;strong&gt;Ingress&lt;/strong&gt; tab: enable ingress, &lt;strong&gt;accept traffic from anywhere&lt;/strong&gt;,
target port &lt;strong&gt;8080&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Create the app. To verify, open &lt;code&gt;https://&amp;lt;app-url&amp;gt;/healthz&lt;/code&gt;, where
&lt;code&gt;&amp;lt;app-url&amp;gt;&lt;/code&gt; is the URL of the created container app. It should return
&lt;code&gt;{"status":"ok",...}&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The endpoint is available now, but anyone can call it. Let's fix that by&lt;br&gt;
enabling authentication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Turn on Easy Auth:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the created Container App, open the &lt;strong&gt;Authentication&lt;/strong&gt; page (you'll
find it under Security) and click &lt;strong&gt;Add identity provider&lt;/strong&gt;. Pick
&lt;strong&gt;Microsoft&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Let the form create the app registration for you. Leave &lt;strong&gt;App
registration type&lt;/strong&gt; on &lt;em&gt;Create new app registration&lt;/em&gt;, give it a name
(e.g. &lt;code&gt;officeagent-easyauth&lt;/code&gt;). Under &lt;strong&gt;Additional checks&lt;/strong&gt;, set
&lt;strong&gt;Client application requirement&lt;/strong&gt; to &lt;em&gt;Allow requests from any
application&lt;/em&gt;. The
wizard creates the registration, the secret, and the wiring in one step.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Restrict access&lt;/strong&gt; to &lt;em&gt;Require authentication&lt;/em&gt; and &lt;strong&gt;Unauthenticated
requests&lt;/strong&gt; to &lt;em&gt;HTTP 401 Unauthorized&lt;/em&gt; - this is an API, not a website; a
login redirect would only confuse MCP clients.&lt;/li&gt;
&lt;li&gt;Save. &lt;code&gt;https://&amp;lt;app-url&amp;gt;/healthz&lt;/code&gt; now returns &lt;strong&gt;401&lt;/strong&gt;. The client must send an
Entra bearer token for the Easy Auth app. You can check this using the following script:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;az account get-access-token &lt;span class="nt"&gt;--resource&lt;/span&gt; api://&amp;lt;easyauth-client-id&amp;gt; &lt;span class="nt"&gt;--query&lt;/span&gt; accessToken &lt;span class="nt"&gt;-o&lt;/span&gt; tsv&lt;span class="si"&gt;)&lt;/span&gt;
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://&amp;lt;app-url&amp;gt;/healthz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What's next
&lt;/h3&gt;

&lt;p&gt;Now you can use the MCP server with your client. In the next article I will&lt;br&gt;
describe how to connect it to a Copilot Studio agent.&lt;/p&gt;

&lt;p&gt;Let me know in the comments if this article is useful, and a star on &lt;a href="https://github.com/ilia-sokolov/OfficeAgent.NET" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; &lt;br&gt;
helps other developers find it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>dotnet</category>
      <category>mcp</category>
      <category>sharepoint</category>
    </item>
    <item>
      <title>Edit SharePoint Word Documents from Any AI Agent over MCP with OfficeAgent.NET 0.2</title>
      <dc:creator>Ilia Sokolov</dc:creator>
      <pubDate>Mon, 29 Jun 2026 11:17:11 +0000</pubDate>
      <link>https://dev.to/ilia-sokolov/edit-sharepoint-word-documents-from-any-ai-agent-over-mcp-with-officeagentnet-02-4ejm</link>
      <guid>https://dev.to/ilia-sokolov/edit-sharepoint-word-documents-from-any-ai-agent-over-mcp-with-officeagentnet-02-4ejm</guid>
      <description>&lt;p&gt;A few weeks ago I released &lt;a href="https://dev.to/ilia-sokolov/word-document-editing-for-ai-agents-introducing-officeagentnet-01-2cc"&gt;OfficeAgent.NET 0.1&lt;/a&gt;, an open-source .NET library that lets AI agents create and edit real Word documents. Instead of manipulating the Open XML themselves, they describe their intent as a typed, validated change plan, and the library turns it into the Open XML that carries it out: tracked changes, tables, comments, and content controls included. Version 0.1 shipped with an integration for Microsoft Agent Framework (MAF), so you could give those document tools to a custom .NET agent.&lt;/p&gt;

&lt;p&gt;Today I'm introducing version 0.2. It exposes the same workflow over the &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt;, so &lt;em&gt;any&lt;/em&gt; MCP-capable agent can read and edit real Word documents. This version also adds a &lt;strong&gt;SharePoint document provider&lt;/strong&gt;, so agents can work on documents where they are actually stored: directly in SharePoint, one of the most widely used cloud document stores in the enterprise, rather than on a local disk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Any agent, over MCP
&lt;/h3&gt;

&lt;p&gt;MCP is an open standard for how an AI agent discovers and calls tools. OfficeAgent.NET 0.2 ships &lt;code&gt;OfficeAgent.Mcp&lt;/code&gt;, a server (built on the official C# MCP SDK) that projects the document workflow as MCP tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;inspect_document&lt;/code&gt; - a structured map of the document: outline, paragraphs with stable ids, styles, content controls, tables, images, and revisions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;find_in_document&lt;/code&gt; - text search that returns content-verified anchors to target.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;preview_plan&lt;/code&gt; - a dry run of a change plan; nothing is written, the agent gets a before/after report and any validation errors.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;apply_plan&lt;/code&gt; - the all-or-nothing commit, saved through the configured storage.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;register_document&lt;/code&gt; / &lt;code&gt;remove_document&lt;/code&gt; / &lt;code&gt;list_connections&lt;/code&gt; - point the agent at a document to work on, release it afterwards, and discover the connections the host configured.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Copilot Studio, Microsoft 365 Copilot, Claude and Claude Code, OpenAI's Codex, and VS Code support MCP and can use this server.&lt;/p&gt;

&lt;p&gt;The server runs in two hosting modes: stdio, used by locally running agents such as Claude Code and Codex, and an ASP.NET Core app over streamable HTTP, required by hosted agents such as Copilot Studio and Microsoft 365 Copilot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working on documents in SharePoint
&lt;/h3&gt;

&lt;p&gt;OfficeAgent works through document providers. An agent registers a document with a provider and gets back an opaque id, which it then uses to address that document in every operation. 0.2 adds a SharePoint provider alongside the filesystem provider from the initial release.&lt;/p&gt;

&lt;p&gt;The SharePoint provider reads and writes documents directly in SharePoint - the system most organizations in the Microsoft ecosystem use to store their documents. A document is registered by its URL or its Graph &lt;code&gt;driveId/itemId&lt;/code&gt; pair; from there the agent runs the same inspect → preview → apply loop it would run against a local folder.&lt;/p&gt;

&lt;p&gt;Two things make it safe to point an agent at a real document:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You review every change&lt;/strong&gt;. The agent's edits arrive as Word tracked changes - accept or reject them like a colleague's. And each change is verified against the live document, so it edits the exact place you meant or fails safely, instead of silently rewriting the wrong clause.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's scoped to you&lt;/strong&gt;. Acting on behalf of the signed-in user (OAuth 2.0 On-Behalf-Of), the agent can only open and edit what you're already allowed to in SharePoint - not a shared account with broader reach.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Try it yourself
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# the MCP server&lt;/span&gt;
dotnet tool &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; OfficeAgent.Mcp &lt;span class="nt"&gt;--prerelease&lt;/span&gt;

&lt;span class="c"&gt;# the SharePoint provider&lt;/span&gt;
dotnet add package OfficeAgent.SharePoint &lt;span class="nt"&gt;--prerelease&lt;/span&gt;

&lt;span class="c"&gt;# the core library&lt;/span&gt;
dotnet add package OfficeAgent.Core &lt;span class="nt"&gt;--prerelease&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source, samples, and the full hosting and client-setup guides are on GitHub: &lt;a href="https://github.com/ilia-sokolov/OfficeAgent.NET" rel="noopener noreferrer"&gt;https://github.com/ilia-sokolov/OfficeAgent.NET&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Version 0.2 of OfficeAgent.NET opens the document workflow to more agents and more enterprise use cases. Copilot Studio agents, Microsoft 365 Copilot, Claude Code, and Codex can now create or modify Word documents from user intent - without touching OOXML directly, without losing details, and without leaving the user to fix the document afterwards. And with the new SharePoint provider, agents can do it right where most of the documents already live.&lt;/p&gt;

&lt;p&gt;Try it out and tell me what you think - that feedback will shape 0.3. If the direction is useful to you, add a star on GitHub to help other developers find it.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>ai</category>
      <category>mcp</category>
      <category>sharepoint</category>
    </item>
    <item>
      <title>Word Document Editing for AI Agents - Introducing OfficeAgent.NET 0.1</title>
      <dc:creator>Ilia Sokolov</dc:creator>
      <pubDate>Thu, 11 Jun 2026 18:02:29 +0000</pubDate>
      <link>https://dev.to/ilia-sokolov/word-document-editing-for-ai-agents-introducing-officeagentnet-01-2cc</link>
      <guid>https://dev.to/ilia-sokolov/word-document-editing-for-ai-agents-introducing-officeagentnet-01-2cc</guid>
      <description>&lt;p&gt;A few weeks ago I wrote about &lt;a href="https://dev.to/ilia-sokolov/why-ai-agents-struggle-with-word-documents-and-what-to-do-about-it-1kni"&gt;why AI agents struggle with Word documents&lt;/a&gt;. The short version: agents produce text, Markdown, JSON, and HTML, but a .docx is an OOXML package, and most of what makes it a Word document lives in XML parts outside the visible text. Something has to translate the agent's output into a valid Word file, and the Word file back into something the agent can reason about. I called this the agent-document layer, and announced an open-source project to build it for .NET.&lt;/p&gt;

&lt;p&gt;Today the first version is available. &lt;strong&gt;OfficeAgent.NET 0.1&lt;/strong&gt; is an open-source (MIT) .NET library that lets an AI agent describe Word document changes as a typed plan, while the library handles the Open XML details.&lt;/p&gt;

&lt;p&gt;If your agents generate proposals, contracts, reports, or review packs, this layer is the missing piece of document automation between the model and the final .docx. This article is a first look at what the release contains and how it works.&lt;/p&gt;

&lt;h3&gt;
  
  
  The core idea: the agent never writes document bytes
&lt;/h3&gt;

&lt;p&gt;The design decision behind OfficeAgent.NET is simple: the language model never produces .docx content directly.&lt;/p&gt;

&lt;p&gt;Instead, the agent expresses intent as a &lt;strong&gt;plan&lt;/strong&gt;: a typed, JSON-serializable list of operations. Replace this clause as a tracked change. Fill that content control. Add a row to this table. Attach a comment to that paragraph. JSON works well here because models produce it reliably and .NET code can validate it strictly. The library translates the plan into the Open XML manipulations that carry it out.&lt;/p&gt;

&lt;p&gt;Version 0.1 ships 15 operations for text, table, image, and styling manipulations, for comments and document properties, and for accepting or rejecting existing tracked revisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The workflow: inspect → find → preview → commit
&lt;/h3&gt;

&lt;p&gt;Every edit follows the same four steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inspect&lt;/strong&gt; - the agent gets a structured map of the document: the outline, paragraphs with stable ids, styles, content controls, tables, images, and revisions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Find&lt;/strong&gt; - the agent searches for text and gets an address back for each match.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preview&lt;/strong&gt; - the plan is checked against the current document. Nothing is written; the caller gets a before/after report and any validation errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit&lt;/strong&gt; - the plan is applied as one all-or-nothing transaction. If any step fails, nothing is written.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The addresses are called &lt;strong&gt;anchors&lt;/strong&gt;, and they are the safety mechanism. The library issues anchors from inspect and find; the agent reuses them and never invents one. Each anchor carries the content it expects to find, and at commit time the library re-checks it against the live document. If the document changed in the meantime, the operation fails safely instead of editing the wrong place.&lt;/p&gt;

&lt;h3&gt;
  
  
  Document providers: filesystem now, SharePoint and more later
&lt;/h3&gt;

&lt;p&gt;OfficeAgent.NET does not read and write files directly. Documents come from &lt;strong&gt;document providers&lt;/strong&gt;, an abstraction that lets the library work with any file source. The application registers a document with a provider (for the filesystem provider, by its path) and receives an opaque document id in return. From that point on, inspect, find, preview, and commit all address the document by that id, and the provider takes care of loading from and saving to the underlying store.&lt;/p&gt;

&lt;p&gt;Version 0.1 ships a filesystem provider. Other providers, such as SharePoint, a database, or any custom store, can implement the same interface, and more will become available in later versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Editing Word documents from a Microsoft Agent Framework agent
&lt;/h3&gt;

&lt;p&gt;The main scenario for OfficeAgent.NET is an agent doing the editing itself. For that, the workflow is exposed as agent tools over &lt;code&gt;Microsoft.Extensions.AI&lt;/code&gt;: &lt;code&gt;inspect_document&lt;/code&gt;, &lt;code&gt;find_in_document&lt;/code&gt;, &lt;code&gt;preview_plan&lt;/code&gt;, and &lt;code&gt;apply_plan&lt;/code&gt;. Wiring them into a Microsoft Agent Framework agent looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Agents.AI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.AI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;OfficeAgent.AgentFramework&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;OfficeAgent.Core&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;OfficeAgent.Core.DocumentProviders&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;OfficeAgent.Word&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ServiceCollection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddWordFormat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddFileSystemDocumentProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"workspace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/srv/officeagent/workspace"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOfficeAgent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildServiceProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OfficeAgentClient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Register the document with the provider; get an opaque id back.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"workspace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/srv/officeagent/workspace/contract.docx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OfficeAgentTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsAIFunctions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"You are editing documentId="&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
           &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ItemId&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
           &lt;span class="s"&gt;" on connectionId=workspace.\n\n"&lt;/span&gt;
           &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;OfficeAgentTools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SystemPromptGuidance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;AIAgent&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChatClientAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// any Microsoft.Extensions.AI IChatClient&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="s"&gt;"OfficeAgent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"Edits Word documents using OfficeAgent.NET."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;        &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cast&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AITool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From here the agent drives the document itself: it inspects, finds anchors, builds a plan, previews it, and applies it through the tools. &lt;code&gt;OfficeAgentTools.SystemPromptGuidance&lt;/code&gt; provides ready-made instructions that teach the model the inspect → plan → apply protocol. &lt;code&gt;apply_plan&lt;/code&gt; saves the result and returns an output document id rather than sending .docx bytes back through the model; the host reads the result from storage and delivers the file through its own download or attachment API. A runnable Azure OpenAI example is in the &lt;code&gt;samples/AgentEdit&lt;/code&gt; project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using OfficeAgent.NET as a .NET library
&lt;/h3&gt;

&lt;p&gt;The same workflow can also be driven directly from C# code, without a model in the loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// service registration and document setup are the same&lt;/span&gt;
&lt;span class="c1"&gt;//as in the agent example&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OfficeAgentClient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"workspace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/srv/officeagent/workspace/contract.docx"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// inspect → find → preview → commit, addressing the document by its id&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InspectAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"workspace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ItemId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"workspace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ItemId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FindQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Acme Corp"&lt;/span&gt;&lt;span class="p"&gt;))).&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;DocumentPlan&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Snapshot&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// opt in to drift detection&lt;/span&gt;
    &lt;span class="n"&gt;Operations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PlanOperation&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ChangeTextOp&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Target&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Anchor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;With&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Globex Inc."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChangeMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tracked&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;preview&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PreviewAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"workspace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ItemId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsValid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* surface preview.Errors */&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CommitAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"workspace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ItemId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Committed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenReadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// saved.Stream holds the edited bytes;&lt;/span&gt;
    &lt;span class="c1"&gt;//result.Document.ItemId is the new id.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tracked edit lands as a real Word revision, with insert and delete runs that show up in Word's review pane, not as flattened text. The &lt;code&gt;samples/QuickEdit&lt;/code&gt; project in the repository contains a runnable version of this flow.&lt;/p&gt;

&lt;h3&gt;
  
  
  What 0.1 covers, and what it doesn't yet
&lt;/h3&gt;

&lt;p&gt;Version 0.1 covers Word .docx documents and the full editing workflow: inspect, find, preview, and transactional commit, protected by content-verified anchors and optional snapshot-based drift detection. The release includes the 15 operations described above, the filesystem document provider together with the interface other file sources can implement, the agent tools for Microsoft Agent Framework, two runnable samples (&lt;code&gt;QuickEdit&lt;/code&gt; and &lt;code&gt;AgentEdit&lt;/code&gt;), and a documentation set.&lt;/p&gt;

&lt;p&gt;This is a 0.1 preview release. Expect rough edges, and expect some APIs to change based on feedback.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it, and tell me where it breaks
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package OfficeAgent.Core &lt;span class="nt"&gt;--prerelease&lt;/span&gt;
dotnet add package OfficeAgent.Word &lt;span class="nt"&gt;--prerelease&lt;/span&gt;

&lt;span class="c"&gt;# for the agent-tools path&lt;/span&gt;
dotnet add package OfficeAgent.AgentFramework &lt;span class="nt"&gt;--prerelease&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The source, samples, and documentation are on GitHub: &lt;a href="https://github.com/ilia-sokolov/OfficeAgent.NET" rel="noopener noreferrer"&gt;https://github.com/ilia-sokolov/OfficeAgent.NET&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is early work, but the direction is clear: make real Word documents usable from agent workflows without requiring every agent developer to become an OOXML expert. If your agents need to produce real Office documents and this is (or isn't) the shape you would want, open an issue and tell me what's missing. That feedback is the whole point of shipping this early. And if the direction is useful to you, a star on GitHub helps other agent developers find it.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>dotnet</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Why AI agents struggle with Word documents, and what to do about it</title>
      <dc:creator>Ilia Sokolov</dc:creator>
      <pubDate>Fri, 22 May 2026 19:07:06 +0000</pubDate>
      <link>https://dev.to/ilia-sokolov/why-ai-agents-struggle-with-word-documents-and-what-to-do-about-it-1kni</link>
      <guid>https://dev.to/ilia-sokolov/why-ai-agents-struggle-with-word-documents-and-what-to-do-about-it-1kni</guid>
      <description>&lt;p&gt;For a lot of professionals in consulting, finance, sales, HR, legal, and operations, the final product is a document: a proposal, contract, report, invoice, brief, or review pack. Nowadays AI agents help people produce these documents. Agents draft individual sections or entire documents. They produce text, Markdown, JSON, and HTML as part of document-production workflows, and they can create basic &lt;code&gt;.docx&lt;/code&gt; files from scratch. But if you ask them to modify an existing document, or to produce a new one that follows an example template - the result usually disappoints. The produced documents frequently contain one or more of these problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fonts, colors, paragraph spacing, header and footer elements silently disappear.&lt;/li&gt;
&lt;li&gt;Headings get replaced with formatted text.&lt;/li&gt;
&lt;li&gt;Numbered lists turn into literal numbers.&lt;/li&gt;
&lt;li&gt;Tables get flattened or lose formatting.&lt;/li&gt;
&lt;li&gt;Tracked changes and comments vanish from the output.&lt;/li&gt;
&lt;li&gt;Content controls are replaced with text instead of populating them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Word documents are hard for agents
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;.docx&lt;/code&gt; is not a text file. It's an OOXML package: a zipped set of XML files containing all document elements and metadata. Most of what makes a Word document a Word document lives in those side files, not in the visible text.&lt;/p&gt;

&lt;p&gt;Agents produce plain text, Markdown, JSON, and HTML - not formats that preserve Word's document semantics by themselves. Even when an agent uses a Word-specific library to process documents, it usually works with the visible content and misses structure and styling parts that live outside the text, so styles, numbering, tracked changes, and comments get dropped.&lt;/p&gt;

&lt;h2&gt;
  
  
  The missing translation layer
&lt;/h2&gt;

&lt;p&gt;Something has to translate the agent's output into a valid Word file - and translate the Word file back into something the agent can reason about. Call this the agent-document layer.&lt;/p&gt;

&lt;p&gt;In one direction, the layer describes the document to the agent in agent-native terms: sections, paragraphs, headings, tables, content controls, comments, and review locations. The agent sees a document model it can reason about.&lt;/p&gt;

&lt;p&gt;In the other direction, the agent responds with a typed change plan. JSON works well for this: it's structured, explicit, easy for models to produce, and easy for .NET code to validate. The agent says what it wants (e.g. insert this clause as a tracked change, attach this comment to that paragraph, add this row to that table) and the layer applies the plan to the &lt;code&gt;.docx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The Open XML manipulations are managed by the layer, not by the agent or the application developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The .NET landscape today
&lt;/h2&gt;

&lt;p&gt;If you operate in the .NET landscape, several categories of tools are available for working with Word documents:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Raw OOXML control&lt;/strong&gt;. Tools like Microsoft Open XML SDK give full access to the &lt;code&gt;.docx&lt;/code&gt; internals, but developers still have to manage Word structure manually.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rich-text conversion&lt;/strong&gt;. HTML or Markdown-to-Word pipelines are useful for simple generation, but often fail to preserve numbering, comments, tracked changes, and review structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Template filling&lt;/strong&gt;. Placeholder tools work well for fixed templates, but are too limited for clauses, tables, redlines, or comments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Word object models&lt;/strong&gt;. Libraries like DocX, OfficeIMO, and NPOI make Word editing easier, but the application still decides every structural change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full document platforms&lt;/strong&gt;. Aspose.Words, GemBox.Document, and TX Text Control provide broad document automation, but they are not designed to act as a contract between an AI agent and a Word file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All these tools can be building blocks for the agent-document layer, but none of them are the layer itself. They don't let an agent express document intent and receive back a valid, reviewable .docx that preserves the user's template or sample document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing OfficeAgent.NET
&lt;/h2&gt;

&lt;p&gt;I'm starting OfficeAgent.NET to build this layer for .NET: &lt;a href="https://github.com/ilia-sokolov/OfficeAgent.NET" rel="noopener noreferrer"&gt;https://github.com/ilia-sokolov/OfficeAgent.NET&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The goal is to give AI agents and .NET applications a simple way to describe Word document changes in structured terms, then let the library handle the OOXML details. The first focus is Word: opening existing &lt;code&gt;.docx&lt;/code&gt; templates, filling content controls, inserting structured tables, producing tracked-change suggestions, adding comments, and saving documents that preserve the user's template.&lt;/p&gt;

&lt;p&gt;This is early work, but the direction is clear: make real Word documents usable from agent workflows without requiring every agent developer to become an OOXML expert.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>dotnet</category>
      <category>ooxml</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
