If you've been using Claude Code (or any AI tool that supports MCP), you've probably noticed that its real power comes from connecting it to external systems. In this article I'll walk through building a custom MCP server in Python that lets AI tools create, edit, and format Microsoft Word documents using the python-docx library.
By the end, you'll have a working MCP server you can register with Claude Code and then instruct it in plain English to generate Word documents: contracts, reports, form letters — whatever your business needs.
What is MCP?
Model Context Protocol (MCP) is an open standard developed by Anthropic that defines how AI assistants communicate with external tools. Think of it as a plugin system: you write a server that exposes a set of "tools" (functions), and any MCP-compatible AI client — including Claude Code — can discover and call those tools.
The protocol uses JSON-RPC over stdio or HTTP. Your MCP server is just a process that responds to tool-call requests. The AI describes what it wants to do; your server does the actual work and returns a result.
Prerequisites
- Python 3.10 or higher
- Claude Code installed (
npm install -g @anthropic-ai/claude-code) - Basic Python familiarity
Install the required packages:
pip install mcp python-docx
mcp Python package is Anthropic's official SDK for building MCP servers. It handles all the protocol plumbing so you can focus on writing your tools.
Project structure
We'll keep this simple:
word-mcp-server/
├── server.py # The MCP server
└── requirements.txt
Building the server
Here's the complete server.py. I'll explain each section below.
from mcp.server.fastmcp import FastMCP
from docx import Document
from docx.shared import Pt, RGBColor, Inches
from docx.enum.text import WD_ALIGN_PARAGRAPH
import os
mcp = FastMCP("Word Automation")
@mcp.tool()
def create_document(file_path: str, title: str = "") -> str:
"""Create a new Word document at the given path, with an optional title heading."""
doc = Document()
if title:
heading = doc.add_heading(title, level=0)
heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
doc.save(file_path)
return f"Created document: {file_path}"
@mcp.tool()
def add_paragraph(file_path: str, text: str, style: str = "Normal") -> str:
"""Add a paragraph to an existing Word document.
Style options: Normal, Heading1, Heading2, Heading3, Quote
"""
doc = Document(file_path)
style_map = {
"Normal": "Normal",
"Heading1": "Heading 1",
"Heading2": "Heading 2",
"Heading3": "Heading 3",
"Quote": "Quote",
}
doc.add_paragraph(text, style=style_map.get(style, "Normal"))
doc.save(file_path)
return f"Added paragraph to {file_path}"
@mcp.tool()
def add_table(file_path: str, headers: list[str], rows: list[list[str]]) -> str:
"""Add a table to an existing Word document.
headers: list of column header strings
rows: list of rows, each row is a list of cell strings
"""
doc = Document(file_path)
table = doc.add_table(rows=1, cols=len(headers))
table.style = "Table Grid"
# Header row
header_row = table.rows[0].cells
for i, header in enumerate(headers):
header_row[i].text = header
run = header_row[i].paragraphs[0].runs[0]
run.bold = True
# Data rows
for row_data in rows:
row = table.add_row().cells
for i, cell_text in enumerate(row_data):
row[i].text = cell_text
doc.save(file_path)
return f"Added {len(rows)}-row table to {file_path}"
@mcp.tool()
def add_page_break(file_path: str) -> str:
"""Add a page break to an existing Word document."""
doc = Document(file_path)
doc.add_page_break()
doc.save(file_path)
return f"Added page break to {file_path}"
@mcp.tool()
def read_document_text(file_path: str) -> str:
"""Read all text content from an existing Word document."""
doc = Document(file_path)
paragraphs = [p.text for p in doc.paragraphs if p.text.strip()]
return "\n\n".join(paragraphs) if paragraphs else "(document is empty)"
@mcp.tool()
def list_document_styles(file_path: str) -> str:
"""List all available paragraph styles in a Word document."""
doc = Document(file_path)
styles = [s.name for s in doc.styles if s.type.name == "PARAGRAPH"]
return "\n".join(sorted(styles))
if __name__ == "__main__":
mcp.run()
What each tool does
create_document — Creates a new .docx file at a given path. Optionally adds a centered title heading. This is always the first tool called when generating a new document from scratch.
add_paragraph — Appends a paragraph to an existing document with an optional style. The style map keeps the Claude-facing API simple while mapping to Word's verbose internal style names.
add_table — Adds a formatted table given a list of headers and a list of rows. Each row is a list of strings. The header row is automatically bolded.
add_page_break — Adds a page break, useful for multi-section documents like reports.
read_document_text — Returns the plain text content of a document, useful for verifying what was generated or for editing existing documents.
list_document_styles — Lists available paragraph styles so Claude can make informed choices about styling.
Registering the server with Claude Code
Claude Code reads MCP server configurations from its settings file. On Windows this is at %USERPROFILE%\.claude\settings.json. Add your server under the mcpServers key:
{
"mcpServers": {
"word": {
"command": "python",
"args": ["C:/path/to/word-mcp-server/server.py"],
"env": {}
}
}
}
After saving, restart Claude Code. You can verify it loaded by running /mcp in a Claude Code session — you should see your "word" server listed with its tools.
Using it in a Claude Code session
Once registered, you can direct Claude Code to generate Word documents in plain English:
Create a one-page service agreement at C:/Docs/service-agreement.docx.
Include: title "Service Agreement", a section for Services Provided,
a section for Payment Terms, a section for Term and Termination,
and a signature block at the end.
Claude Code will call your MCP tools in sequence — create_document, then multiple add_paragraph calls with appropriate styles — and produce a properly formatted .docx file.
Extending the server
The six tools above are a useful foundation, but you can go much further. Some extensions worth adding:
- Bullet lists — Add a tool that accepts a list of strings and creates a bulleted list using the "List Bullet" style
- Bold/italic runs — Add a tool for appending a paragraph where specific words are formatted inline
- Header and footer — Set company name, date, or page numbers in document headers/footers
- Insert image — Use
doc.add_picture()to embed a logo or chart - Replace text — Open an existing template document and replace placeholder strings (useful for mail merge-style workflows)
The pattern is always the same: write a Python function, decorate it with @mcp.tool(), give it a clear docstring (Claude reads this to understand when and how to use the tool), and save the file.
Real-world uses
For Atlanta businesses, a Word automation MCP server is immediately useful for:
- Generating client proposals or statements of work from a standard template
- Producing weekly or monthly reports from database query results
- Creating form letters, notices, or contracts in bulk
- Automating compliance documentation that follows a consistent structure
In each case, a few minutes of prompting replaces what used to be 30–60 minutes of manual document assembly.
Next steps
If you want to go deeper: the python-docx documentation covers the full API for styles, sections, and formatting. And Anthropic's MCP specification documents the full protocol if you want to add features like prompts or resource subscriptions beyond simple tools.
If you're building something like this for your business and want help with the implementation, feel free to get in touch. Document automation is one of the more satisfying problems to solve — the value is immediately obvious and the ROI is usually fast.