Skip to main content
Glama

letterwriter-mcp

An MCP server that generates DOCX letters on your organization's own letterhead and returns the document as an attachment.

Point an AI assistant at it and "write Ms. Okonkwo a closing letter for her Riverside case" produces a correctly branded Word document, signed by the right office, that someone can review and send.

It speaks MCP over streamable HTTP, so it works with LibreChat, Claude Desktop, or anything else that speaks the protocol. It is useful to any organization that sends letters on headed paper — legal aid, clinics, nonprofits — and knows nothing about the deployment it came from.

Tools

Tool

Does

create_letter

Renders a letter and returns the .docx itself

list_letterheads

Lists the letterhead IDs and labels available

create_letter returns the document as MCP binary content — an embedded resource carrying the bytes — rather than a download link. There is no object storage here, no credentials to leak, and no URL that expires and turns a letter from last month into a dead link.

Related MCP server: docs-mcp

Your letterheads never live in this repository

This repository ships one template: a 1.3 KB placeholder that says [ YOUR LETTERHEAD ARTWORK GOES HERE ]. Your own letterheads stay outside it.

At runtime the server reads a registry — a letterheads.json describing your offices — and the .docx files it names:

${LETTERHEAD_DIR}/            # default /data/letterheads
├── letterheads.json
├── baltimore-city.docx
├── montgomery-county.docx
└── ...

If ${LETTERHEAD_DIR}/letterheads.json exists, that registry and those templates are used. If it does not, the server falls back to the copy committed here, so a fresh install starts and can produce a letter before anyone has uploaded anything.

/healthz reports which one is in force:

{"ok":true,"service":"letterwriter-mcp","version":"1.0.0",
 "letterheads":{"count":13,"default":"generic",
                "registry":"/data/letterheads/letterheads.json",
                "using_bundled_registry":false}}

Check using_bundled_registry first when every letter comes out on the wrong letterhead. A LETTERHEAD_DIR that is empty or never mounted leaves the placeholder in charge, and everything else looks perfectly healthy.

The registry

{
  "defaultId": "generic",
  "stopWords": ["office", "example legal aid"],
  "letterheads": [
    {
      "id": "riverside",
      "label": "Riverside",
      "file": "riverside.docx",
      "aliases": ["riverside", "riverside branch", "north county"],
      "legalserver_offices": ["Riverside Branch"],
      "include_unit_name": true
    }
  ]
}

Field

Means

id

Stable machine name; what letterhead_id accepts

label

Human name, shown by list_letterheads

file

A bare filename in the same directory as the registry

aliases

Free-text spellings an assistant might be handed for this office

legalserver_offices

Exact office names as LegalServer spells them, if you use it

include_unit_name

When true, a unit_name argument is added to this office's signature block

defaultId

Which letterhead to use when nothing matches

stopWords

Words ignored when matching. Add your organization's own name, so "Example Legal Aid — Riverside Office" matches riverside.

The whole registry is validated at startup and every problem is reported at once — a missing .docx, a duplicate id, a defaultId matching nothing. Finding those one letter at a time, in production, while someone waits to send mail to a client, is the experience this avoids.

Matching prefers the longest alias, so riverside suite 300 beats riverside rather than depending on the order of the file.

Making a template

Copy templates/letterheads/generic.docx, put your artwork in it, and keep the placeholders. They are docxtemplater tags:

{today} {client_name} {address1} {address2} {honorific} {client_last_name} {message_body} {attorney} {signature_lines}

{signature_lines} is filled with your organization name, the office name, and — for offices setting include_unit_name — the unit.

Running it

docker run -d --name letterwriter-mcp \
  -e ORGANIZATION_NAME="Example Legal Aid" \
  -e MCP_ALLOWED_HOSTS=letterwriter-mcp,localhost,127.0.0.1 \
  -v /srv/letterheads:/data/letterheads:ro \
  -p 127.0.0.1:3002:3002 \
  ghcr.io/marylandlegalaid/letterwriter-mcp:v1.0.0

Variable

Default

Means

ORGANIZATION_NAME

Your Organization

First line of every signature block. Set this.

LETTERHEAD_DIR

/data/letterheads

Your registry and templates

MCP_HTTP_HOST

127.0.0.1

0.0.0.0 in a container

MCP_HTTP_PORT

3002

MCP_ALLOWED_HOSTS

unset

See below

MCP_SHARED_SECRET

unset

Optional shared-secret header; no-ops when unset

LETTERHEAD_REGISTRY_PATH

Override just the registry path

LETTERHEAD_TEMPLATES_DIR

Override just the template directory

MCP_ALLOWED_HOSTS will bite you. The MCP SDK rejects requests whose Host header it does not recognize. Behind Docker Compose the caller sends the service name, so that name has to be in this list or every request is refused. Symptom: the server is healthy, the client reports a connection error, and nothing obviously connects the two.

With LibreChat

mcpSettings:
  allowedAddresses:
    - "letterwriter-mcp:3002"      # LibreChat blocks internal hostnames without this

mcpServers:
  LetterWriter:
    type: streamable-http
    url: http://letterwriter-mcp:3002/letter-writer/mcp
    description: "Create DOCX letters on organization letterhead"
    chatMenu: true

allowedAddresses is not optional. LibreChat treats a Docker service name as an SSRF target and refuses every connection without it, reporting Domain ... is not allowed.

A worked deployment — Compose service, storage, backups — is in MarylandLegalAid/librechat-azure.

Trust boundary

This server has no authentication by default, on purpose. It is built to run on a private container network reachable only by the application that calls it, and that network is the boundary.

Set MCP_SHARED_SECRET if you ever put it anywhere else. Note also that it renders whatever text it is given onto your letterhead: whoever can reach it can produce a document that looks official.

Development

npm install
npm test            # 29 tests, no network, no fixtures on disk
npm start

License

MIT — see LICENSE. No warranty; see the license text.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    MCP server that lets agents edit real Microsoft Word (.docx) documents - tracked changes, tables, styles, comments, content controls, and document properties - with every edit validated and previewed before saving. Built on the Open XML SDK (no Word automation); reads and writes documents in place through filesystem or SharePoint storage.
    Last updated
    7
    12
    MIT

View all related MCP servers

Related MCP Connectors

  • Use your own Word templates to convert Markdown → DOCX/PDF/HTML from any MCP-compatible AI.

  • Generate PDF/DOCX/XLSX/PPTX from templates+JSON. Convert Office/HTML/MD to PDF. Universal templating

  • Fill standard legal agreement templates (NDAs, SAFEs, NVCA docs, employment) as DOCX files.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MarylandLegalAid/letterwriter-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server