MCP Document Server
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.
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 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, onTXTextControl.Markdown.Coreand onModelContextProtocol.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:
dotnet add package TXTextControl.AI.McpServer
The following Program.cs hosts the server for development on the local machine only:
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.RunIfRequestedAsyncreturns an exit code when the process was started as a document worker, andnullfor 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.AddTextControlMcpServerregisters the tools, sessions, style presets, operations, cleanup and the document engine. It takes the configuration root, which contains theMcpServer,DocumentWorkerPoolandDocumentAutomationsections.MapTextControlMcpmaps the MCP endpoint at/mcp, the extraction endpoint/mcp/knowledge/extractthat RAG 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 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 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:
{
"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_operationsruns the operations of the enabled capability packs on a session, among theminsert_text,insert_section_break,set_section_layoutandset_header_footer.get_document_automation_capabilitiesreturns the operation names a server has enabled. - Documents live in sessions.
load_documentloads a Base64-encoded document andcreate_document_from_markdowncreates one; both return asessionIdthat 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 namedtxmb_followed by the block name, as Merge Fields and Merge Blocks describe.merge_templatemerges JSON data with MailMerge.MergeJsonData. By default, it doesn't append and removes empty fields, blocks, images, lines and trailing whitespace, which differs from the defaults of theMailMergeclass. - Some tools aren't for models.
get_authoring_guidereturns large schemas and examples and should be requested deliberately.extract_knowledge_blocksis meant for indexing by the host, not for conversations. classify_documentassigns a category such as Legal, Finance or Human Resources without calling a model, as Document Analysis 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:
{
"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:
{
"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:
{
"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
isErrorand a code such asinvalid_argument,not_foundorlicense_error. 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
downloadUriwith an authorized client instead of building a URL or transferring the document as Base64. - End the session.
delete_sessionremoves the session and its files; otherwise, they're kept untilSessionMaxAgeHourshas 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:
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.Mcppackage, which brings theModelContextProtocolclient with it. HttpClientTransportandMcpClientof the MCP SDK connect to the endpoint with the Streamable HTTP transport. Authentication of the connection is configured on this transport.McpDocumentToolSet.CreateAsyncdiscovers the tools.Toolscontains them in the form a chat client passes to a model, andServerInstructionsholds the instructions the server sends for their use.OutputDirectoryis the private folder for exported files, andExportBaseUrithe only origin that downloads may come from. The download client has redirects disabled, as the package requires.- The model doesn't see
get_as_base64by default. The tool set adds a localsave_documenttool 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:
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.
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; for the document server, use its own URL instead of the public endpoint.
A server outside development requires credentials, as 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:
{
"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:
{
"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 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:
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:
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 orMcpServiceAuthentication:Requiredistrue, 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 unlessRequiredistrue. - Every request to
/mcpor/exportsmust use HTTPS and sendAuthorization: Bearerfollowed 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 aWWW-Authenticate: Bearerheader. - 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 asMcpServiceAuthentication__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:
// 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 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: let a model plan document actions with the tools.
- Text Control AI: run the web sample that uses the document server.
- RAG: extract reference documents through the server for retrieval.
- AI Coding Assistants: connect assistants to the public Text Control MCP Server.
- Examples: the MCP hosts and the MCP document server repository.