Skip to main content
Glama
trsdn

io.github.trsdn/mcp-server-word

by trsdn

WordMcp — Microsoft Word MCP Server

Сервер MCP, который позволяет ИИ-ассистентам управлять Microsoft Word для Windows через COM-автоматизацию: открывать документы, читать и редактировать текст, управлять абзацами и таблицами, задавать свойства документа и экспортировать в PDF.

Только Windows. Требуется локальная установка Microsoft Word — этот сервер управляет реальным приложением, а не разбирает файлы .docx.


Требования

ОС

Windows 10/11

Среда выполнения

.NET 9 SDK или среда выполнения

Office

Microsoft Word 2016 или новее (настольная версия, не версия из Microsoft Store)

Related MCP server: Word Document MCP Server

Установка

dotnet tool install --global WordMcp.McpServer

После этого инструмент доступен как mcp-word.

mcp-word --version
mcp-word --help

Чтобы позже обновить или удалить его:

dotnet tool update --global WordMcp.McpServer
dotnet tool uninstall --global WordMcp.McpServer

Чтобы вместо этого запускать невыпущенную сборку, упакуйте её локально и установите из выходной папки:

dotnet pack src\WordMcp.McpServer\WordMcp.McpServer.csproj -c Release -o artifacts
dotnet tool install --global --add-source .\artifacts WordMcp.McpServer

Без установки

Сервер значится в реестре MCP как io.github.trsdn/mcp-server-word. Клиенты, которые сами разрешают пакеты, могут запускать его через dnx, который загружает версию по требованию, а не держит глобальный инструмент:

{
  "servers": {
    "word": {
      "type": "stdio",
      "command": "dnx",
      "args": ["WordMcp.McpServer@0.1.0", "--yes"]
    }
  }
}

Настройка клиента

Сервер работает через stdio.

VS Code / GitHub Copilot

.vscode/mcp.json:

{
  "servers": {
    "word": {
      "type": "stdio",
      "command": "mcp-word"
    }
  }
}

Claude Desktop

%APPDATA%\Claude\claude_desktop_config.json:

{
  "mcpServers": {
    "word": {
      "command": "mcp-word"
    }
  }
}

Copilot CLI

copilot mcp add word --command mcp-word

Понятия

Каждая операция выполняется в рамках сеанса. Сеанс владеет одним экземпляром Word и одним открытым документом и идентифицируется по session_id, например word-a1b2c3d4e5f6g.

file(open|create) ──► session_id ──► text / paragraph / table / document ──► file(save) ──► file(close)
  • Пути должны быть абсолютными (C:\Users\me\Documents\report.docx).

  • Поддерживаемые входные форматы: .docx, .docm, .doc, .dotx, .dotm, .rtf.

  • Документ не должен быть уже открыт в Word — WordMcp нужен исключительный доступ.

  • Word незаметно работает в фоне и завершается, когда сеанс закрывается.

Служба сеансов

Сеансы обычно живут внутри процесса MCP-сервера и исчезают вместе с ним. WordMcp.Service.exe — это опциональный фоновый демон, который хранит их вместо этого, поэтому сеанс переживает перезапуск клиента и может использоваться несколькими клиентами:

WordMcp.Service.exe --daemon [--idle-minutes 30]   # listen until idle or stopped
WordMcp.Service.exe --status                       # what is it doing?
WordMcp.Service.exe --stop                         # save open documents and exit

Запускать его вручную почти не требуется — клиент, настроенный на его использование, запускает его по требованию. Канал, который он слушает, содержит ваш SID и ограничен ACL для него, поэтому сеансы никогда не разделяются между учётными записями. Он завершается сам, если после истечения тайм-аута простоя не было открыто ни одного сеанса.

Чтобы использовать его, задайте WORDMCP_SERVICE_MODE=daemon для MCP-сервера. Тогда каждый вызов инструмента уходит к демону, а не выполняется в собственном процессе сервера. Без этого сервер держит сеансы у себя, что как раз нужно одному клиенту: никакого второго процесса и никакого ожидания при запуске.


Инструменты

Пятнадцать инструментов, каждый с параметром action.

file — жизненный цикл сеанса

Действие

Назначение

open

Открыть существующий документ и начать сеанс

create

Создать новый документ по пути path

save

Сохранить открытый документ

close

Сохранить (если нужно) и закрыть сеанс

list

Перечислить все активные сеансы

test

Проверить, можно ли автоматизировать Word на этом компьютере

file(action: "open", path: "C:\\Users\\me\\Documents\\report.docx")
// → { "sessionId": "word-a1b2c3d4e5f6g", "fileName": "report.docx", ... }

text — содержимое

Действие

Назначение

get

Прочитать весь текст или диапазон символов (start, end, max_length)

append

Добавить текст, при необходимости новым абзацем

find

Найти термин; возвращает позиции и окружающий контекст

replace

Заменить вхождения (match_case, match_whole_word, replace_all)

format

Применить bold, italic, underline, font_name, font_size, color к диапазону

Позиции символов получают из get и find и являются смещениями диапазонов Word.

paragraph — структура

Действие

Назначение

list

Перечислить абзацы с индексом, текстом, стилем, выравниванием и уровнем структуры

add

Добавить абзац, при необходимости со стилем style

insert

Вставить абзац перед указанным индексом

delete

Удалить абзац по индексу

set-style

Применить стиль, например Heading 1

set-alignment

left, center, right или justify

Индекс абзаца начинается с 1, как в Word.

table — таблицы

Действие

Назначение

list

Перечислить таблицы с размерами и стилем

create

Создать таблицу с параметрами rows × columns

read

Прочитать все ячейки таблицы как матрицу строк и столбцов

set-cell

Записать одну ячейку (row, column, text)

add-row

Добавить строку

delete-row

Удалить строку

set-style

Применить стиль таблицы, например Table Grid

document — метаданные и экспорт

Действие

Назначение

get-info

Количество слов, знаков, абзацев, страниц, таблицы и разделов

get-properties

Заголовок, автор, тема, ключевые слова, комментарии, компания

set-properties

Изменить эти встроенные свойства

export-pdf

Экспортировать в PDF, не затрагивая открытый документ

save-as

Сохранить копию в другом формате

image — изображения

Действие

Назначение

list

Перечислить встроенные изображения с индексом, размером, alt-текстом и состоянием ссылки

insert

Вставить изображение, опционально с width, height, caption и alt_text

resize

Изменить размер с помощью width/height или scale_percent

replace

Заменить изображение по индексу, сохраняя его размер по умолчанию

delete

Удалить изображение по индексу

set-alt-text

Установить альтернативный текст для доступности

Тип — поля и оглавления

Действие

Назначение

list

Перечислить все поля с индексом, типом и кодом поля

insert-toc

Вставить оглавление (up_per_heading_level, lower_heading_level)

update-toc

Пересчитать каждое оглавление

update-all

Обновить все поля, включая поля верхних и нижних колонтитулов

insert-page-number

Добавить номер страницы в верхний или нижний колонтитул

section — разделы и параметры страницы

Действие

Назначение

list

Перечислить все разделы с типом начала, полями, размером страницы и ориентацией

add

Вставить разрыв раздела (start_type: next-page, continuous, even-page, odd-page)

page-setup

Задать поля, orientation и paper_size для одного раздела или всего документа

Действие

Назначение

get

Прочитать верхний или нижний колонтитул одного раздела или всех разделов

set

Записать текст, возможно с alignment

clear

Очистить верхний или нижний колонтитул

kind выбирает header или footer, а type — primary, first-page или even-pages.

style — стили

Действие

Назначение

list

Перечислить стили; по умолчанию те, что используются в документе

create

Создать новый стиль, возможно на основе существующего

modify

Изменить шаблон и форматирование абзаца для стиля

delete

Удалить пользовательский стиль

style_type выбирает paragraph, character или table. Укажите in_use_only: false в list, чтобы получить полный набор, который в локализованном Word содержит более 370 записей.

style(action: "create", session_id: "...", name: "Callout", base_style: "Normal")
style(action: "modify", session_id: "...", name: "Callout",
      font_size: 11, bold: true, color: "#C00000", space_after: 12)

list — маркеры и нумерация

Действие

Назначение

get

Сообщить форматирование списка абзацев, включая обрабатываемый маркер или номер

apply

Оформить диапазон абзацев как список: bullet, number или outline-number

set-level

Установить уровень списка для диапазона (1–9)

restart

Начать нумерацию заново с абзаца

remove

Снять форматирование списка

Если end_index не указан, действие применяется только к start_index.

list(action: "apply", session_id: "...", start_index: 2, end_index: 5, list_type: "number")
list(action: "set-level", session_id: "...", start_index: 3, end_index: 4, level: 2)
list(action: "restart", session_id: "...", start_index: 6)

comment — комментарии рецензента

Действие

Назначение

list

Перечислить комментарии с автором, датой, текстом и комментируемым текстом

добавить

Прикрепить комментарий к абзацу или фразе внутри него

resolved

Пометить комментарий как решённый или открыть его снова

delete

Удалить комментарий

Команда add примечает весь абзац, если anchor_text не указывает на фразу внутри. После delete индексы изменяются, поэтому перед удалением ещё одного комментария выведите свой список заново.

comment(action: "add", session_id: "...", paragraph_index: 4,
        text: "Source?", anchor_text: "fifteen percent")
comment(action: "list", session_id: "...", unresolved_only: true)

revision — отслеживание исправлений

Действие

Назначение

list

Перечислить отслеживаемые исправления и сообщить, включено ли отслеживание

accept

Принять одно исправление или все

reject

Отклонить одно исправление или все

set-tracking

Включить или выключить отслеживание изменений

Если опустить index в accept/reject, действие применится ко всему документу, включая верхние и нижние колонтитулы.

revision(action: "set-tracking", session_id: "...", enabled: true)
revision(action: "accept", session_id: "...")

bookmark — стабильные ссылки

Действие

Назначение

list

Закладки с именем, индексом абзаца и предпросмотром отмеченного текста

add

Создаёт закладку на абзац, диапазон абзацев или фразу внутри абзаца

get-text

Читает полный текст, сохранённый в закладке

delete

Удаляет закладку; текст остаётся

Имена должны начинаться с буквы и могут содержать только буквы, цифры и символы подчёркивания. Закладки переживают правки в других местах документа, поэтому остаются надёжным способом возвращаться к нужному фрагменту, когда индексы абзацев уже сместились.

bookmark(action: "add", session_id: "...", name: "Intro", paragraph_index: 2)
bookmark(action: "add", session_id: "...", name: "Growth",
         paragraph_index: 4, anchor_text: "fifteen percent")
bookmark(action: "get-text", session_id: "...", name: "Intro")

screenshot — посмотреть страницу

Действие

Назначение

page

Отрисовывает страницу в формате PNG

Вопросы вёрстки — разрывы страниц, ширины таблиц, расположение изображений, положение колонтитулов — гораздо проще решать по отрисованной странице, чем по измерениям. PNG записывается в файл, и возвращается путь к нему; include_image: true дополнительно возвращает его встроенным как base64, что имеет смысл только когда на изображение собираются смотреть.

dpi по умолчанию равен 150. Используйте 96 для быстрой проверки вёрстки и 300 для результата, близкого к печати.

screenshot(action: "page", session_id: "...", page: 2)
screenshot(action: "page", session_id: "...", page: 1,
           output_path: "C:/temp/page1.png", dpi: 300, include_image: true)

Ответы

Каждый инструмент возвращает JSON. Ошибки сообщаются структурированными данными, а не транспортной ошибкой:

{
  "success": false,
  "isError": true,
  "tool": "text",
  "action": "Replace",
  "errorType": "KeyNotFoundException",
  "errorMessage": "Session 'word-unknown' not found."
}

Известное поведение и подводные камни

  • document(save-as) также сохраняет исходный файл. У Word нет API для смены формата «сохранить копию». Для любого целевого формата, кроме PDF, сервер вызывает SaveAs2(target) и затем SaveAs2(original), из-за чего несохранённые изменения пишутся в исходный файл как побочный эффект. Используйте export-pdf, когда нужен экспорт без побочных эффектов.

  • Цвета задаются в hex RGB (#0078D4). Сервер преобразует их в BGR-значение, которое ожидает Word.

  • Документы с защитой прав доступа (IRM/AIP) отклоняются ещё до запуска Word.

  • Открытый в Word документ блокирует сеанс — сначала закройте его.

  • Диалоговые окна Word останавливают автоматизацию. Если вызов завершился по таймауту, проверьте, нет ли на рабочем столе открытого диалогового окна. Верхняя наждак? Actually "An open dialog on the desktop": "на рабочем столе".

  • Имена стилей английские. Встроенные стили (Heading 1, Title, Table Grid, …) преобразуются в независимые от языка идентификаторы стилей Word, поэтому работают в локализованных установках. Любое другое имя передаётся в Word как есть; так адресуются пользовательские и локализованные стили. Учтите, что Word выдаёт имена стилей под их локализованным названием (Überschrift 1 в немецкой установке), поэтому style(list) возвращает и name, и english_name — при наличии english_name передавайте в ответ именно его.

  • Встроенные стили нельзя удалить. style(delete) отклоняет запрос понятным сообщением, не пропуская общую COM-ошибку Word. Пользовательский стиль, который всё ещё применяется к абзацу, тоже нельзя удалить; сначала присвойте таким абзацам другой стиль.

  • Новые документы записываются напрямую, а не через Word. file(create) сам записывает пустой пакет .docx/.docm, а затем открывает его. Создание документов через Word на старых машинах, входящих в Microsoft 365, ненадёжно: AutoSave перехватывает новый документ для OneDrive и молча игнорирует запрошенный локальный путь.

  • Объединённые ячейки таблицы возвращаются функцией table(read) как пустые строки.

  • Размеры изображений указываются в пунктах, а не в пикселях (72 пт = 1 дюйм). image(insert) и image(resize) сохраняют пропорции, если lock_aspect_ratio не установлен в false, поэтому при передаче только width высота масштабируется пропорционально.

  • image поддерживает только встроенные рисунки. Плавающие фигуры, надписи и диаграммы не затрагиваются и не появляются в image(list), поэтому их наличие не сдвигает индексы изображений.

  • Оглавление перечисляет только абзацы‑заголовки. Для документа без стилей заголовков field(insert-toc) возвращает entry_count: 0 — сначала примените Heading 1/Heading 2 через paragraph(add|set-style), затем выполните field(update-toc).

  • field(update-all) также обходит верхние и нижние колонтитулы. Поле Document.Fields в Word охватывает только простой текст документа, поэтому иначе номера страниц никогда не обновлялись бы.

  • image(insert) с подписью использует нумерацию подписей Word, поэтому подпись выглядит как Figure 1 <your text> (в неанглийских установках — локализованно) и участвует в списке иллюстраций.

  • Все измерения задаются в пунктах, включая поля страницы (1 см = 28,35 пт; 1 дюйм = 72 пт).

  • section(page-setup) применяет paper_size до полей, потому что изменение размера бумаги сбрасывает их в Word. Без section_index настройка применяется каждому разделу.

  • Верхние и нижние колонтитулы наследуются между нами. Новый раздел показывает колонтитулы предыдущего, пока в него что‑нибудь не записано. header-footer(set) с section_index разрывает эту связь автоматически, поэтому раздел сохраняет собственный текст.

  • Колонтитулы первой и чётной страницы требуют переключения раздела. header-footer(set) сам включает DifferentFirstPage и DifferentOddEvenPages соответственно; без этого Word сохраняет текст, но никогда не выводит его.

  • list(apply) по умолчанию начинает новый список. continue_previous_list отключён, потому что продолжать нумерацию постороннего более раннего списка почти никогда не нужно. Два нумерованных списка, разделённых обычными абзацами, остаются независимыми; используйте list(restart), если Word всё же объединяет их.

  • Разные уровни отображают только списки со структурной нумерацией. list(set-level) работает с любым списком, но обычный bullet или number показывает один и тот же маркер на всех уровне — абзацы просто получают отступ.

  • comment(resolve) в Microsoft 365 часто не срабатывает. Современные комментарии Word считают каждый комментарий, добавленный через API, неопубликованным черновиком, а черновик нельзя отметить выполненным. Сервер сообщает об этом ясным сообщением; вместо этого удалите комментарий. На установках, где невозможно выяснить состояние вовсе, comment(list) возвращает resolved: null.

  • Индексы комментариев и ревизий сдвигаются. Удаление комментарияи принятие одной ревизии перенумеровывает всё после них, поэтому между двумя такими вызовами выполняйте list снова, а не не используйте старые индексы.

  • revision(accept|reject) без индекса также обходит колонтитулы. Метод Document.AcceptAllRevisions() в Word охватывает только основную текст документа, так же как у Add-range etc. Actually "same gap as with field(update-all)".

  • Записанные изменения фиксируются только во время их записи. The same as "while tracking is on".

  • Имена закладок ограничены Wordом. They must start with a letter, may contain only letters, digits, underscores, length 40. Spaces, hyphens, dots, non-ASCII letters rejected before Word.

  • Закладки — стабильный способ обращения к фрагменту. Индексы абзацев сдвигаются от каждой вставки, закладки — нет. Создайте закладку один раз и впоследствии читайте текст через bookmark(get-text).

  • bookmark(add) на абзац не включает знак абзаца самого конца, поэтому get-text возвращает текст без финального переноса. Закладка на несколько абзацев сохраняет знаки между ними.

  • screenshot(page) работает через PDF. У Word нет API, возвращающего страницу как изображение, поэтому сервер экспортирует одну страницу через ExportAsFixedFormat и растрирует её. Несохранённые изменения включаются, а временная PDF‑файл потом удаляется.

  • Номера страниц берутся из свежей разбивки на страницы. Документ, редактированный только автоматизацией, показывает устаревшее число страниц, поэтому screenshot сначала выполняет переразбивку. Это также значит, что количество страниц отражает текущий макет, а не состояние на момент открытия.


Сборка из исходного кода

git clone https://github.com/trsdn/mcp-server-word.git
cd mcp-server-word
dotnet build WordMcp.sln -c Release
dotnet test WordMcp.sln --filter "Category!=RequiresWord"

Структура проекта

Проект

Назначение

src/WordMcp.ComInterop

Жизненный цикл COM Word: STA‑потоки, сеансы, фильтр сообщений OLE, проверка файлов

src/WordMcp.Core

Интерфейсы команд, реализации команд и модели результатов

src/WordMcp.Generators.Shared

Исходные файлы, общие для генераторов; не самостоятельный проект

src/WordMcp.Generators.Mcp

Исходный генератор Roslyn, создающий классы инструментов MCP

src/WordMcp.McpServer

stdio MCP‑сервер, предоставляющий пятнадцать инструментов

tests/WordMcp.Core.Tests

Модульные тесты и интеграционные тесты с реальным Word

tests/WordMcp.McpServer.Tests

Модульные тесты слоя инструментов без Word

Генерируемый слой инструментов

Четырнадцать из пятнадцати инструментов генерируются при сборке. Интерфейсы команд в src/WordMcp.Core/Commands — единственный источник правды для контракта передачи данных:

  • [ServiceCategory("section", "Section")] задаёт имя класса инструмента — WordSectionTool.

  • [McpTool("section", Title = ..., Description = ...)] задаёт имя инструмента и подсказку, которую читает модель.

  • [Action("page-setup")] на каждом методе становится значением генерируемого перечисления WordSectionAction.

  • XML-документация параметров интерфейса превращается в описания параметров MCP‑схемы.

Генератор объединяет параметры всех действий в один метод, поэтому параметр, используемый только некоторыми действия, добавляется как необязательный. Чтобы изменить API, правится интерфейс, а не сгенерированный код. file остаётся написанным вручную, потому что управляет параметрами на всех сессиях, а не работает с одним документом.

Сгенерированный код смотрите в src/WordMcp.Mcmcs/obj/generated. Тесты в GeneratedToolContractTests сверяют сгенерированную «поверхность» с интерфейсами, поэтому несоответствие приводит к поправке как проверки сборки, а не к ошибке у клиента.

Тесты, тем которым нужен установленный Word, помечены [Trait("Category", "RequiresWord")] и исключены из CI. После неудачных интеграционных прогонов могут остаться зависшие процессы WINWORD.EXE, которые замедляют или блокируют последующие запуски; очистите их командой Get-Process WINWORD | Stop-Process -Force перед повторным запуском.

Дополнительные материалы

Документ

Что описывает

docs/architecture.md

Слои, поток запросов и то, как генерируется слой инструментов

docs/com-interop.md

STA‑потоки, освобождение COM‑объектов и поведение Word, стоящее за пунктами выше

CONTRIBUTING.md

Сборка, тестрование, добавление инструмента, подготовка релиза

skills/word-mcp/SKILL.md

Ориентное агентом руководство оприменению инструментов в правильном порядке

Устранение неполадок

Симптом

Причина и решение

Word is not installed or not registered for COM

Установите настольную версию Word; версия Store не поддерживает автоматизацию

Could not load file or assembly 'office'

office.dll не найден в GAC — переустановите или восстановите Office

Операция истекла по времени

Диалог Word ожидает ввода; закройте его и повторите.

The file is already open in Word

Закройте документ в окне Word.

Участие в разработке

Сообщения об ошибках и запросы на новые функции проходят через шаблоны issue. Пул-реквесты приветствуются; в CONTRIBUTING.md описано, как выполнить сборку, как запустить обе части тестового набора и что нужно, чтобы добавить инструмент.

Пожалуйста, сначала прочитайте Кодекс поведения.

Не создавайте публичных issue о проблемах безопасности — сообщите о них в частном порядке, как описано в политике безопасности.

Лицензия

MIT — см. LICENSE.

Related MCP Connectors

Related MCP Servers