# MCP Document Server

> Host TX Text Control document tools for AI clients with TXTextControl.AI.McpServer: MCP basics, hosting, configuration, the tools, clients and security.

- **Product:** TX Text Control .NET Server
- **Section:** Documentation
- **Kind:** Topic
- **Path:** TX Text Control .NET Server > AI Integration > MCP Document Server
- **7873 words**
- **Web URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/mcp-server/
- **LLMs URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/mcp-server/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/mcp-server/llms-full.txt

---

Asked for a contract as PDF, a language model can write the wording, but it isn't a document engine that knows native styles, tables and merge fields. With the Model Context Protocol (MCP), the model calls tools instead: the MCP document server of TX Text Control creates, inspects, edits, converts and exports real documents, and the model decides which of its tools to call.

The server comes as `TXTextControl.AI.McpServer`, a package of the TX Text Control AI preview that was checked for this page in version 0.1.0-beta.3. Its APIs and tools may change before a stable release. How a model uses the tools in a loop, and how its edits are verified, is described in [Agent Workflows](https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/agent-workflows/llms-full.txt).

### What MCP Is

MCP is an open standard for how AI applications connect to servers that offer tools. A client asks a server for its tools, each with a JSON schema of its parameters, and calls them with structured arguments. The messages are JSON-RPC 2.0 requests over HTTP: `tools/list` returns the tools, and `tools/call` runs one. Any client that implements MCP can use any MCP server, without integration code for each pair.

The MCP document server isn't another language model. It's the component that knows document formats, paragraph styles, tables, fields and exports. Compared with a model that writes document files directly, tools give:

- **Consistent results**, because TX Text Control performs every operation the same way.
- **Styles defined by the application**, which the server applies to the documents it creates.
- **Fewer tokens**, because a tool call is compact and the document internals never pass through the model.
- **Real formats**, such as DOCX, PDF and TX.
- **Follow-up edits**, because the document stays in a session on the server.

### Two MCP Servers From Text Control

Text Control offers two different MCP servers:

|  | Text Control MCP Server | MCP document server |
|---|---|---|
| Purpose | Gives AI coding assistants read-only access to blog articles, product information and API documentation | Creates, edits, converts and exports documents with TX Text Control |
| Where it runs | Hosted by Text Control at `https://www.textcontrol.com/mcp/v1` | In your own ASP.NET Core application, with the `TXTextControl.AI.McpServer` package |
| Changes data | No | Yes |

[AI Coding Assistants](https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/coding-assistants/llms-full.txt) describes the public endpoint and how assistants connect to it. The rest of this page is about the MCP document server.

### The Package

`TXTextControl.AI.McpServer` is a hosting library, not a ready-to-run application: it adds the MCP endpoint and the document tools to an ASP.NET Core application that you build.

- **Included.** Document workflows, native styles, tables, fields, templates, exports and a bounded pool of document workers.
- **Not included.** Administration pages, a login, a language model or an inference engine. The server works independently of any AI provider, chat model or website.
- **Requirements.** .NET 10 with ASP.NET Core 10 and appropriately licensed TX Text Control products. The package depends on `TXTextControl.TextControl.Core.SDK`, which comes from the [Text Control Private NuGet Feed](https://docs.textcontrol.com/tx-text-control-dotnet-server/installation/nuget/private-feed/llms-full.txt), on `TXTextControl.Markdown.Core` and on `ModelContextProtocol.AspNetCore`.
- **Platforms.** The document engine runs on Windows and Linux, not natively on macOS. Linux needs the libraries and fonts that the TX Text Control SDK requires. An AI service that calls the server can run on a Mac.
- **Fonts.** A build target copies the default fonts of the SDK into the build and publish output. Install additional fonts for your documents yourself; a font name in a style preset doesn't install that font.

The C# namespace of the package is `TxTextControl.McpServer`.

### Host the Server

Add the prerelease package to an ASP.NET Core project that targets .NET 10:

```bash
dotnet add package TXTextControl.AI.McpServer --prerelease
```

The following *Program.cs* hosts the server for development on the local machine only:

```csharp
using TxTextControl.McpServer;

// Must run first: the worker pool starts this application again as a document worker
if (await TextControlMcpWorker.RunIfRequestedAsync(args) is int exitCode)
{
    Environment.ExitCode = exitCode;
    return;
}

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseUrls("http://127.0.0.1:5000");
builder.Services.AddTextControlMcpServer(builder.Configuration);

var app = builder.Build();
app.MapTextControlMcp();
app.Run();
```

The code works as follows:

- `TextControlMcpWorker.RunIfRequestedAsync` returns an exit code when the process was started as a document worker, and `null` for a normal start. It runs before the web host is built and before any console output, because the standard output of a worker carries its communication with the pool.
- `AddTextControlMcpServer` registers the tools, sessions, style presets, operations, cleanup and the document engine. It takes the configuration root, which contains the `McpServer`, `DocumentWorkerPool` and `DocumentAutomation` sections.
- `MapTextControlMcp` maps the MCP endpoint at `/mcp`, the extraction endpoint `/mcp/knowledge/extract` that [RAG](https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/rag/#extract-docx-pdf-and-html-documents) uses and the downloads of exported files at `/exports`. Map it on the application itself, not inside a route group with a prefix.

The endpoint uses the Streamable HTTP transport of MCP. Opening `/mcp` in a browser isn't a tool call; test the server with an MCP client. When you publish the application, deploy the *runtimeconfig* and *deps* files and the native files with it, and set `DocumentWorkerPool:WorkerExecutablePath` when the application runs behind IIS or a service wrapper.

The [txtextcontrol-ai-samples](https://github.com/TextControl/txtextcontrol-ai-samples) repository contains two hosts. `MinimalMcpHost` runs only in the Development environment, rejects requests from other computers and listens at `http://127.0.0.1:5082/mcp`. `McpServer` adds administration pages for settings, capability packs and styles at `http://127.0.0.1:5000/admin`; [Run the Samples](https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/text-control-ai/#run-the-samples) describes how to start it.

### Configure the Server

The settings come from *appsettings.json* or from environment variables with double underscores, such as `DocumentWorkerPool__WorkerCount`:

```json
{
  "McpServer": { "BasePath": "documents", "SessionMaxAgeHours": 12 },
  "DocumentWorkerPool": { "Enabled": true, "WorkerCount": 2, "QueueCapacity": 128 },
  "DocumentAutomation": {
    "DefaultParagraphStyleName": "Body",
    "StyleRoles": {
      "Title": "Title", "Heading1": "Heading", "Heading2": "Heading2", "Body": "Body"
    },
    "StylePresets": [
      { "Name": "Title", "FontName": "Arial", "FontSize": 24, "Bold": true },
      { "Name": "Heading", "FontName": "Arial", "FontSize": 16, "Bold": true },
      { "Name": "Heading2", "FontName": "Arial", "FontSize": 13, "Bold": true },
      { "Name": "Body", "FontName": "Arial", "FontSize": 11 }
    ],
    "DefaultPageLayout": {
      "PageSize": "A4", "Orientation": "portrait", "Unit": "mm",
      "MarginTop": 20, "MarginBottom": 20, "MarginLeft": 20, "MarginRight": 20
    }
  }
}
```

| Setting | Default | Sets |
|---|---|---|
| `McpServer:BasePath` | `samples` | The private, writable folder for documents and sessions; never a folder of static web content |
| `McpServer:SessionMaxAgeHours` | `12` | How long sessions and their files are kept before cleanup |
| `DocumentWorkerPool:Enabled` | `true` | Whether documents are processed in separate worker processes; `false` uses the engine in the process of the application |
| `DocumentWorkerPool:WorkerCount` | `2` | The number of document workers, from 1 to 16 |
| `DocumentWorkerPool:QueueCapacity` | `128` | The number of waiting commands; a larger queue doesn't add processing capacity |
| `DocumentWorkerPool:CommandTimeoutSeconds` | `180` | How long a document command may take |
| `DocumentAutomation:EnabledCapabilityPacks` | `null` | The enabled groups of operations: `BasicText`, `Tables`, `Fields`, `Media`, `Sections` and `HeaderFooter`; `null` enables all, an empty list none |
| `DocumentAutomation:EnabledOperations` | `null` | The enabled single operations, with the same rules |
| `DocumentAutomation:StylePresets` | empty | The paragraph styles that new documents use |
| `DocumentAutomation:StyleRoles` | `Title`, `Heading`, `Heading2`, `Body` | Which preset is used for the title, the first two heading levels and body text |
| `DocumentAutomation:DefaultPageLayout` | `null` | The page size, orientation and margins of new documents |

An operation is available only when both its pack and the operation itself are enabled. A disabled operation doesn't necessarily disappear from the tool list, so check the effective set with the `get_document_automation_capabilities` tool. Sessions are kept by one host and aren't a distributed store, which matters before you run several instances of the server.

### The Document Tools

The README of the package documents the tools below. The `tools/list` response of the deployed server is authoritative, because tools change between versions.

| Group | Tools |
|---|---|
| Sessions and files | `load_document`, `create_empty_document`, `convert_document`, `create_document_export`, `get_as_base64`, `get_session`, `delete_session` |
| Creation | `create_document_from_markdown`, `create_document`, `list_document_recipes`, `create_document_from_recipe`, `get_authoring_guide`, `get_document_automation_capabilities` |
| Reading | `inspect_document`, `inspect_document_section`, `classify_document`, `get_text`, `get_paragraphs`, `search_text`, `search_text_ranges`, `get_document_model`, `get_document_structure`, `get_document_styles`, `get_document_tables`, `get_document_fields`, `get_document_headers_footers`, `get_template_merge_fields`, `get_template_merge_blocks`, `get_template_form_fields`, `extract_knowledge_blocks` |
| Editing | `edit_document`, `replace_document_section`, `format_text`, `format_text_occurrences`, `format_paragraph`, `apply_operations`, `apply_document_preset_styles` |
| Styles | `list_document_styles`, `set_document_style`, `apply_document_style`, `rename_document_style`, `delete_document_style`, `create_styles_from_paragraphs` |
| Tables | `insert_table`, `format_table`, `add_table_rows` |
| Fields and templates | `insert_merge_field`, `update_merge_field`, `create_merge_block`, `insert_form_field`, `update_form_field`, `clear_application_fields`, `clear_form_fields`, `merge_template` |

- **Sections, headers and footers are operations.** `apply_operations` runs the operations of the enabled capability packs on a session, among them `insert_text`, `insert_section_break`, `set_section_layout` and `set_header_footer`. `get_document_automation_capabilities` returns the operation names a server has enabled.
- **Documents live in sessions.** `load_document` loads a Base64-encoded document and `create_document_from_markdown` creates one; both return a `sessionId` that later calls reuse. The formats are TX, RTF, DOCX, PDF, HTML, Markdown and plain text, as far as the operation supports them. Conversions between formats aren't guaranteed to be lossless, and PDF import needs readable text because there is no OCR.
- **Fields are real fields.** Merge fields are MERGEFIELD fields, not `{{name}}` text, and merge blocks are subtext parts named `txmb_` followed by the block name, as [Merge Fields](https://docs.textcontrol.com/tx-text-control-dotnet-server/mail-merge/fields/llms-full.txt) and [Merge Blocks](https://docs.textcontrol.com/tx-text-control-dotnet-server/mail-merge/blocks/llms-full.txt) describe. `merge_template` merges JSON data with [MailMerge.MergeJsonData](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol-documentserver/mailmerge/mergejsondata-method/llms-full.txt). By default, it doesn't append and removes empty fields, blocks, images, lines and trailing whitespace, which differs from the defaults of the `MailMerge` class.
- **Some tools aren't for models.** `get_authoring_guide` returns large schemas and examples and should be requested deliberately. `extract_knowledge_blocks` is meant for indexing by the host, not for conversations.
- **`classify_document`** assigns a category such as Legal, Finance or Human Resources without calling a model, as [Document Analysis](https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/analysis/#classify-documents) describes.

### Call a Tool

A client sends a `tools/call` request with the name of the tool and its arguments. Most tools take one `request` object. The following request creates a document from Markdown:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_document_from_markdown",
    "arguments": {
      "request": { "markdown": "# Review checklist\n\n- Verify names\n- Approve changes" }
    }
  }
}
```

The result contains the `sessionId` of the new document. An edit passes it on and names the text to replace:

```json
{
  "name": "edit_document",
  "arguments": {
    "request": {
      "sessionId": "RETURNED_SESSION_ID",
      "matchText": "Approve changes",
      "replacementText": "Obtain manager approval",
      "replaceAll": false
    }
  }
}
```

After a successful edit, `create_document_export` saves the document in a format and returns its metadata, including a `downloadUri`:

```json
{
  "name": "create_document_export",
  "arguments": {
    "request": {
      "sessionId": "RETURNED_SESSION_ID", "format": "pdf", "fileName": "checklist.pdf"
    }
  }
}
```

- **Check the result, not the HTTP status.** A call can succeed on the transport level and still return an error with `isError` and a code such as `invalid_argument`, `not_found` or `license_error`. [Verify Before Reporting Success](https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/agent-workflows/#verify-before-reporting-success) describes what a successful edit or export returns.
- **Download the returned URI.** The export is stored in the session and expires with it. Download it from the exact `downloadUri` with an authorized client instead of building a URL or transferring the document as Base64.
- **End the session.** `delete_session` removes the session and its files; otherwise, they're kept until `SessionMaxAgeHours` has passed.

### Connect a .NET Application

`TXTextControl.AI.Mcp` is the matching client library for .NET 8. It connects to the server, discovers its tools and saves exported files without passing their content through a model:

```csharp
using Microsoft.Extensions.AI;
using ModelContextProtocol.Client;
using TXTextControl.AI.Mcp;

var endpoint = new Uri("http://127.0.0.1:5000/mcp");

var transport = new HttpClientTransport(new HttpClientTransportOptions
{
    Name = "TX Text Control MCP",
    Endpoint = endpoint,
    TransportMode = HttpTransportMode.StreamableHttp
});

await using var mcp = await McpClient.CreateAsync(transport);

// Downloads must not follow redirects
using var downloads = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false });

var documents = await McpDocumentToolSet.CreateAsync(mcp, downloads, new McpDocumentToolSetOptions
{
    OutputDirectory = Path.GetFullPath("private-exports"),
    ExportBaseUri = new Uri(endpoint.GetLeftPart(UriPartial.Authority))
});

Console.WriteLine($"Advertised tools: {documents.DiscoveredToolCount}");
```

The code works as follows:

- The project references the prerelease `TXTextControl.AI.Mcp` package, which brings the `ModelContextProtocol` client with it.
- `HttpClientTransport` and `McpClient` of the MCP SDK connect to the endpoint with the Streamable HTTP transport. Authentication of the connection is configured on this transport.
- `McpDocumentToolSet.CreateAsync` discovers the tools. `Tools` contains them in the form a chat client passes to a model, and `ServerInstructions` holds the instructions the server sends for their use.
- `OutputDirectory` is the private folder for exported files, and `ExportBaseUri` the only origin that downloads may come from. The download client has redirects disabled, as the package requires.
- The model doesn't see `get_as_base64` by default. The tool set adds a local `save_document` tool instead, so that the model receives only the metadata of a saved file.

The application can also export a document of an existing session directly, without a model:

```csharp
DocumentExportResult exported = await documents.SaveDocumentAsync(
    sessionId, format: "pdf", fileName: "approved-document.pdf");

Console.WriteLine(exported.Path);
```

`Path` is a local path on the server, not a public download URL. Disposing the client doesn't delete the sessions on the server. Passing the tools to a model is described in [Agent Workflows](https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/agent-workflows/llms-full.txt).

### Connect AI Assistants

Desktop agents and AI coding assistants that support MCP, such as Claude Desktop or Codex, address a hosted document server by its URL. Where each assistant expects the entry is listed in [Connect Your Assistant](https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/coding-assistants/#connect-your-assistant); for the document server, use its own URL instead of the public endpoint.

A server outside development requires credentials, as [Secure the Server](#secure-the-server) describes. The assistant sends them as an `Authorization` header from its configuration. Keep the key itself out of configuration files that you commit. Visual Studio Code, for example, asks for an input variable once and stores its value:

```json
{
  "inputs": [
    {
      "type": "promptString",
      "id": "tx-document-server-key",
      "description": "API key of the MCP document server",
      "password": true
    }
  ],
  "servers": {
    "tx-document-server": {
      "type": "http",
      "url": "https://your-server.example.com/mcp",
      "headers": {
        "Authorization": "Bearer ${input:tx-document-server-key}"
      }
    }
  }
}
```

Claude Code expands environment variables in the headers of *.mcp.json*, so the key can come from a variable such as `TX_MCP_API_KEY`:

```json
{
  "mcpServers": {
    "tx-document-server": {
      "type": "http",
      "url": "https://your-server.example.com/mcp",
      "headers": {
        "Authorization": "Bearer ${TX_MCP_API_KEY}"
      }
    }
  }
}
```

The assistant should first query the capabilities of the server and then use its tools instead of creating document files itself. Follow-up requests, such as a style change after a document was created, reuse the same `sessionId`. The blog article [AI Natural Language Document Generation with MCP and TX Text Control .NET](https://www.textcontrol.com/blog/2026/07/16/ai-natural-language-document-generation-with-mcp-server-and-tx-text-control-dotnet/) walks through prompts that create documents, templates with merge blocks and forms.

### Secure the Server

The package doesn't authenticate users and doesn't check who owns a session; knowing a session ID isn't proof of access. Before the server is reachable by others, add TLS, authentication, checks of session ownership, quotas, request size limits and a retention policy. The endpoint can be protected in two ways.

#### Require an API Key

The `McpServer` sample protects the MCP endpoint and the downloads with a random key that every caller sends as a bearer token. Its middleware is in *McpHostAccess.cs*:

```csharp
using System.Security.Cryptography;
using System.Text;

namespace TxTextControl.McpServer;

/// <summary>Optional bearer protection for the standalone host's document APIs.</summary>
public static class McpHostAccess
{
    public static void UseMcpHostAccess(this WebApplication app)
    {
        string key = app.Configuration["McpServiceAuthentication:ApiKey"] ?? "";
        bool required = app.Configuration.GetValue<bool>("McpServiceAuthentication:Required");
        if (!required && key.Length == 0) return;
        if (key.Length < 32) throw new InvalidOperationException("MCP service authentication requires a random API key of at least 32 characters.");
        byte[] expected = SHA256.HashData(Encoding.UTF8.GetBytes("Bearer " + key));
        app.Use(async (context, next) =>
        {
            if (context.Request.Path.StartsWithSegments("/mcp") || context.Request.Path.StartsWithSegments("/exports"))
            {
                string supplied = context.Request.Headers.Authorization.ToString();
                if (!context.Request.IsHttps || supplied.Length > 2048 ||
                    !CryptographicOperations.FixedTimeEquals(expected, SHA256.HashData(Encoding.UTF8.GetBytes(supplied))))
                {
                    context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                    context.Response.Headers.WWWAuthenticate = "Bearer";
                    context.Response.Headers.CacheControl = "no-store";
                    return;
                }
            }
            await next();
        });
    }
}
```

*Program.cs* adds the middleware before it maps the server:

```csharp
var app = builder.Build();
app.UseMcpHostAccess();
app.MapTextControlMcp();
app.Run();
```

The code works as follows:

- The key comes from the setting `McpServiceAuthentication:ApiKey`. When a key is configured or `McpServiceAuthentication:Required` is `true`, the key must have at least 32 characters, or the application doesn't start. Without both, the middleware does nothing, which is why the sample refuses to start outside the Development environment unless `Required` is `true`.
- Every request to `/mcp` or `/exports` must use HTTPS and send `Authorization: Bearer` followed by the key. The middleware compares hashes of the expected and the supplied header in constant time and answers any other request with status 401 and a `WWW-Authenticate: Bearer` header.
- The key is a secret. Generate it, for example with `openssl rand -hex 32`, and pass it from a secret store or an environment variable such as `McpServiceAuthentication__ApiKey`, never from a committed *appsettings.json*.

An AI host that calls the protected server sends the key from its `LocalAI:McpCredentials` settings, with `Endpoint` and `Token` for each entry. The administrator login of the sample protects only its administration pages, not the MCP endpoint.

#### Use the Authentication of Your Application

When the users or services that call the server sign in through the authentication of your application, protect the route group that `MapTextControlMcp` returns with an authorization policy:

```csharp
// Register the authentication of your application, for example with your identity provider
builder.Services.AddAuthorizationBuilder()
    .AddPolicy("DocumentAccess", policy => policy.RequireAuthenticatedUser());

var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapTextControlMcp().RequireAuthorization("DocumentAccess");
app.Run();
```

`AddPolicy` registers the policy `DocumentAccess`, which here only admits authenticated users; a policy can also require roles or claims. `RequireAuthorization` applies the policy to the endpoints of the group. [Policy-based authorization in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies) describes policies in detail.

#### Beyond Authentication

- **Session ownership.** Authentication alone doesn't keep one user from the sessions of another. Your application has to enforce which user may access which session and which exported file.
- **Error messages.** Tool errors can contain exception details. Review what your logs and responses reveal before users see them.
- **Storage.** Keep the document folder out of static files, and delete sessions deliberately or rely on the configured cleanup.
- **Shared use.** The sample hosts are meant for private single-user setups. For shared use, add an identity provider, ownership per user or tenant, rate limits, network restrictions and redacted logging.

### Next Steps

- [Agent Workflows](https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/agent-workflows/llms-full.txt): let a model plan document actions with the tools.
- [Text Control AI](https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/text-control-ai/llms-full.txt): run the web sample that uses the document server.
- [RAG](https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/rag/llms-full.txt): extract reference documents through the server for retrieval.
- [AI Coding Assistants](https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/coding-assistants/llms-full.txt): connect assistants to the public Text Control MCP Server.
- [Examples](https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/examples/llms-full.txt): the MCP hosts and the MCP document server repository.

---

## Nearby

- **Previous:** [RAG](https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/rag/llms-full.txt)
- **Next:** [Agent Workflows](https://docs.textcontrol.com/tx-text-control-dotnet-server/ai/agent-workflows/llms-full.txt)
