Mutagen MCP
MCP server for working with Bethesda plugins like esm esp esl using mutagen works library
Ask AI about Mutagen MCP
Powered by Claude · Grounded in docs
I know everything about Mutagen MCP. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
mutagen-mcp
MCP-сервер (stdio) на .NET 9 + Mutagen для чтения Skyrim SE (и родственных релизов Mutagen с ToSkyrimRelease()): статус путей, список из plugins.txt (пагинация offset / maxPlugins, фильтр только отсутствующие на диске), заголовки, мастера, компактный режим mutagen_verify_masters без массива рёбер, а также ограниченный анализ записей (цепочка оверрайдов по FormKey, сравнение двух плагинов, кандидаты ITM / удалённые записи). Тяжёлые сценарии ограничены параметрами и настройками (maxPlugins, maxNodes, MaxConflictScanRecords, …).
Требования
- .NET SDK 9 (на машине с игрой обычно Windows; сборка кроссплатформенная; в Docker образ собирается сам).
- Путь к
plugins.txt(список и порядок плагинов для игры или профиля MO2). DataFolder— первый корень поиска (обычноSkyrim Special Edition\Dataс ванильными мастерами).PluginSearchRoots— дополнительные корни (плоские каталоги с.esp/.esm/.eslили кореньmodsMod Organizer 2). Порядок важен: последний корень в списке выигрывает, если одно и то же имя файла встречается в нескольких корнях.- Для MO2 (плагины в
mods/<имя мода>/…, а не в корнеmods/): задайтеModlistTxtPath— путь кmodlist.txtпрофиля. Резолвер ищет файл какmodsRoot/<папка из modlist>/(Data/)?/<имя плагина>; приоритет как в MO2: ниже в modlist — выше приоритет при совпадении имён.
Если PluginSearchRoots пуст, используется только DataFolder. Без ModlistTxtPath дополнительные корни обрабатываются только плоско (корень/файл и при ProbeDataSubfolder — корень/Data/файл).
Заметка: это не дублирует Mod Organizer MCP: тот про управление профилем MO2; mutagen-mcp использует те же файлы, чтобы открыть бинарные плагины через Mutagen.
Сборка
dotnet build MutagenMcp.sln -c Release
Бинарь: src/MutagenMcp/bin/Release/net9.0/mutagen-mcp.dll.
Запуск в контейнере
MCP по stdio нуждается в интерактивном stdin у контейнера (-i). Смонтируйте Data, при MO2 — mods и modlist.txt, plugins.txt профиля; передайте внутренние пути в env.
Сборка образа (из корня репозитория):
docker build -t mutagen-mcp:local .
Минимальный пример (только игровой Data + plugins.txt):
docker run -i --rm \
-v "/path/to/Skyrim Special Edition/Data:/data:ro" \
-v "/path/to/profiles/MyProfile/plugins.txt:/config/plugins.txt:ro" \
-e MutagenMcp__DataFolder=/data \
-e MutagenMcp__PluginsTxtPath=/config/plugins.txt \
mutagen-mcp:local
Mod Organizer 2 (игра + каталог mods + modlist.txt того же профиля, что и plugins.txt):
docker run -i --rm \
-v "/path/to/Skyrim Special Edition/Data:/data:ro" \
-v "/path/to/MO2/mods:/mods:ro" \
-v "/path/to/MO2/profiles/MyProfile/plugins.txt:/config/plugins.txt:ro" \
-v "/path/to/MO2/profiles/MyProfile/modlist.txt:/config/modlist.txt:ro" \
-e MutagenMcp__DataFolder=/data \
-e MutagenMcp__PluginsTxtPath=/config/plugins.txt \
-e MutagenMcp__ModlistTxtPath=/config/modlist.txt \
-e MutagenMcp__PluginSearchRoots__0=/mods \
mutagen-mcp:local
Пример с плоским вторым корнем (без MO2-структуры папок):
docker run -i --rm \
-v "/path/to/Skyrim Special Edition/Data:/data:ro" \
-v "/path/to/mods/staging:/mods:ro" \
-v "/path/to/profiles/MyProfile/plugins.txt:/config/plugins.txt:ro" \
-e MutagenMcp__DataFolder=/data \
-e MutagenMcp__PluginSearchRoots__0=/mods \
-e MutagenMcp__PluginsTxtPath=/config/plugins.txt \
mutagen-mcp:local
Фрагмент для Cursor (Linux / WSL; пути замените на свои; образ локально: mutagen-mcp:local):
{
"mcpServers": {
"mutagen-mcp": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-v", "/mnt/g/Games/steamapps/common/Skyrim Special Edition/Data:/data:ro",
"-v", "/mnt/s/MO2_data/mods:/mods:ro",
"-v", "/mnt/s/MO2_data/profiles/Default/plugins.txt:/config/plugins.txt:ro",
"-v", "/mnt/s/MO2_data/profiles/Default/modlist.txt:/config/modlist.txt:ro",
"-e", "MutagenMcp__DataFolder=/data",
"-e", "MutagenMcp__PluginsTxtPath=/config/plugins.txt",
"-e", "MutagenMcp__ModlistTxtPath=/config/modlist.txt",
"-e", "MutagenMcp__PluginSearchRoots__0=/mods",
"-e", "MutagenMcp__CacheGameEnvironment=true",
"mutagen-mcp:local"
]
}
}
}
На Windows с Docker Desktop пути в -v часто задают как D:/Games/.../Data:/data:ro. Флаг :ro необязателен, но удобен для чтения.
При MutagenMcp__CacheGameEnvironment=true в Docker имеет смысл задать лимит памяти контейнера (например --memory=8g в args перед образом), если лоад большой.
Ограничения: контейнер должен видеть те же файлы, что и ваша установка. В Linux-контейнере на смонтированном NTFS возможны проблемы с регистром имён файлов относительно plugins.txt.
Процесс MCP, Docker и кэши
- Нет отдельного «вечного» контейнера под MCP: при типичном конфиге
docker run -i --rm …Cursor запускает контейнер на время одной сессии MCP (stdio к процессуdotnet …/mutagen-mcp.dllвнутри контейнера). Выключили MCP или перезагрузили окно — процесс завершился, с--rmконтейнер удаляется. Следующий старт — снова новый контейнер и пустая память процесса. - Кэш резолва путей (класс
PluginPathResolver, RAM процесса, на диск не пишется): накопительный словарь «имя файла плагина → абсолютный путь» (или пометка, что файла нет). Общий для пакетногоResolvePluginPathsи одиночногоTryResolve. Сбрасывается при смене списка корней (DataFolder+PluginSearchRoots), пути или mtimemodlist.txt, флагаProbeDataSubfolder. Порядокplugins.txtна физическое расположение файлов на диске не влияет — в ключ инвалидации он не входит. Для MO2 резолв по папкам модов делается пакетно (перечисление.esp/.esm/.eslв каталоге мода), без комбинаторного числа отдельных проверок «мод × плагин». mutagen_plugin_master_closureперед обходом мастеров один раз вызывает пакетный резолв для всех включённых плагинов изplugins.txt, чтобы заполнить этот словарь; дальнейшие обращения к тем же именам идут из кэша.CacheGameEnvironment— отдельный кэш: полныйGameEnvironmentMutagen в памяти до смены лоада/сигнатуры; см. таблицу ниже. С кэшем путей к файлам он не смешивается.
Конфигурация
src/MutagenMcp/appsettings.json, секция MutagenMcp:
| Ключ | Описание |
|---|---|
DataFolder | Первый корень поиска плагинов (часто игровой Data) |
PluginSearchRoots | Дополнительные корни; более поздние важнее при совпадении имён файлов в разных корнях |
ModlistTxtPath | Путь к MO2 modlist.txt; включает разрешение путей внутри PluginSearchRoots по папкам модов |
ProbeDataSubfolder | Если true, для каждого корня (и для mods/<мод>/) проверяются и …/file.esp, и …/Data/file.esp (по умолчанию true) |
PluginsTxtPath | Абсолютный путь к plugins.txt |
GameRelease | Имя enum Mutagen, по умолчанию SkyrimSE |
PrependImplicitStandardMasters | Если true (по умолчанию), к резолвящемуся лоаду (полный GameEnvironment, mutagen_formid_override_chain и т.д.) в начало добавляются стандартные мастера SSE (Skyrim.esm, Update.esm, DLC), если они есть на диске, но нет среди включённых строк plugins.txt (типичный MO2). Только Skyrim-релизы; false — старое поведение «только plugins.txt» |
MaxPluginsPerRequest | Верхняя граница для параметра maxPlugins у mutagen_list_plugins и mutagen_verify_masters (1–2000) |
MaxConflictScanRecords | Потолок записей при скане одного плагина в mutagen_plugin_record_flags_report (1–100000) |
MaxCompareResults | Потолок пар FormKey в mutagen_compare_two_plugins (1–10000) |
MaxOverrideChainLength | Верхняя граница параметра maxChainLength у mutagen_formid_override_chain (1–256). Это только обрезка массива chain в JSON, не лимит «сколько плагинов проверить» при полном обходе |
OverrideChainUseBinaryOverlay | Если true (по умолчанию), mutagen_formid_override_chain не строит полный GameEnvironment, а открывает каждый плагин через binary overlay и ищет FormKey по top-level группам Mutagen (ContainsKey на группе), без полного перебора всех записей мода. false — прежний путь с полным лоадом и кэшем окружения |
OverrideChainOverlayMaxDegreeOfParallelism | Для overlay-ветки: сколько плагинов открывать параллельно (1 — по очереди). 0 — авто: от 1 до min(число_ядер, 8). Больше — быстрее на SSD при длинном plugins.txt; меньше — если упираетесь в диск или RAM |
CacheGameEnvironment | Если true, один полный GameEnvironment переиспользуется между вызовами тяжёлых инструментов, пока не изменятся plugins.txt, пути плагинов или связанные опции (выше потребление RAM) |
EnvironmentCacheMaxAgeSeconds | При CacheGameEnvironment и значении > 0 кэш сбрасывается по таймеру (секунды); 0 — только инвалидация по сигнатуре файлов |
ExposeToolTimingsInJson | Если true (по умолчанию), тяжёлые conflict-tools добавляют в JSON поле timings (мс), чтобы видеть длительность без чтения stderr |
ToolDiagnosticsLogPath | Если задан абсолютный путь к файлу, сервер дописывает в него строки NDJSON (один JSON на строку): события кэша окружения, сборки GameEnvironment, успехи и ошибки conflict-tools с теми же таймингами, что в логах и при ExposeToolTimingsInJson. Пустая строка — выключено. Каталог создаётся при необходимости |
ToolDiagnosticsProgressPluginInterval | В mutagen_formid_override_chain каждые N позиций лоадордера пишется строка OverrideChainScanProgress в NDJSON-лог (0 — отключить). По умолчанию 64. Перед overlay-сканом пишется событие OverrideChainResolvedListings: prepMs (от старта инструмента до готовности листингов), readListingsMs (чтение лоада через Mutagen из plugins.txt), resolvePathsMs (поиск файлов на диске / MO2), плюс счётчики вроде pluginCount |
Переменные окружения (перекрывают JSON): MutagenMcp__DataFolder, MutagenMcp__PluginsTxtPath, MutagenMcp__ModlistTxtPath, MutagenMcp__GameRelease, MutagenMcp__PrependImplicitStandardMasters, MutagenMcp__MaxPluginsPerRequest, MutagenMcp__MaxConflictScanRecords, MutagenMcp__MaxCompareResults, MutagenMcp__MaxOverrideChainLength, MutagenMcp__OverrideChainUseBinaryOverlay, MutagenMcp__OverrideChainOverlayMaxDegreeOfParallelism, MutagenMcp__CacheGameEnvironment, MutagenMcp__EnvironmentCacheMaxAgeSeconds, MutagenMcp__ExposeToolTimingsInJson, MutagenMcp__ToolDiagnosticsLogPath, MutagenMcp__ToolDiagnosticsProgressPluginInterval, MutagenMcp__ProbeDataSubfolder, MutagenMcp__PluginSearchRoots__0, MutagenMcp__PluginSearchRoots__1, …
Cursor / MCP (без Docker)
Пример (путь к проекту и пути к Data / MO2 подставьте свои):
{
"mcpServers": {
"mutagen-mcp": {
"command": "dotnet",
"args": [
"run",
"--project",
"C:/path/to/mutagen-mcp/src/MutagenMcp/MutagenMcp.csproj",
"-c",
"Release",
"--no-build"
],
"env": {
"MutagenMcp__DataFolder": "D:/Steam/steamapps/common/Skyrim Special Edition/Data",
"MutagenMcp__PluginsTxtPath": "C:/ModOrganizer/profiles/Default/plugins.txt",
"MutagenMcp__ModlistTxtPath": "C:/ModOrganizer/profiles/Default/modlist.txt",
"MutagenMcp__PluginSearchRoots__0": "C:/ModOrganizer/mods",
"MutagenMcp__CacheGameEnvironment": "true"
}
}
}
}
После первой сборки можно вызывать dotnet exec .../mutagen-mcp.dll вместо dotnet run, если удобнее.
Готовый фрагмент для вставки в конфиг MCP Cursor (с кэшем окружения): docs/cursor-mcp.example.json — скопируйте содержимое в свой mcp.json / настройки MCP и поправьте пути. Новые аргументы tools (offset, onlyFileMissing, includeDirectEdges, …) не требуют правок mcp.json: схема приходит от сервера при подключении; достаточно пересобрать образ или обновить бинарь и перезапустить MCP.
Длинные ответы: пагинация и «только проблемы»
| Инструмент | Параметры | Зачем |
|---|---|---|
mutagen_list_plugins | offset (по умолчанию 0), maxPlugins, onlyEnabled, onlyFileMissing | Листать длинный лоадордер страницами; при onlyFileMissing: true в выборку попадают только плагины из plugins.txt, для которых нет файла на диске по текущим корням (после фильтра onlyEnabled). |
mutagen_list_plugins | ответ | totalMatching, returnedCount, count (= returnedCount), limit, offset, hasMore / truncated, onlyFileMissing. Поля header и headerReadError в строках плагина опускаются, если null (меньше JSON). |
mutagen_list_plugins | includeHeaders | Ошибка чтения заголовка одного файла → headerReadError на этой строке, вызов целиком не падает. |
mutagen_verify_masters | includeDirectEdges (по умолчанию true) | При false массив directEdges в JSON не включается (остаются missingMasters, orderViolations, cycles, …); число рёбер по-прежнему в counts.directEdges. Удобно при большом лоадордере, когда нужны только отчёты об ошибках. |
Полный проход mutagen_verify_masters по всем включённым плагинам по-прежнему ограничен maxPlugins и серверным MaxPluginsPerRequest.
Инструменты
| Имя | Назначение |
|---|---|
mutagen_status | serverVersion — версия сборки (в Docker: 0.x.y+docker. + UTC-метка в publish, чтобы проверить, что запущен свежий образ), плюс пути к plugins.txt / modlist.txt, GameRelease, корни поиска |
mutagen_list_plugins | Список из plugins.txt; файлы ищутся по DataFolder + PluginSearchRoots (+ разбор MO2 при ModlistTxtPath). Пагинация: offset + maxPlugins; в ответе totalMatching, hasMore / truncated, returnedCount (поле count — то же число). onlyFileMissing — только плагины без файла на диске. При includeHeaders ошибка чтения заголовка даёт headerReadError на строке, а не сбой всего вызова |
mutagen_plugin_header | Заголовок одного плагина; путь через те же правила резолва |
mutagen_plugin_major_records_sample | Первые N major-записей из одного плагина через binary overlay (без полного GameEnvironment); в ответе есть formKey — удобно подобрать ключ для mutagen_formid_override_chain |
mutagen_plugin_lookup_form_key | Один плагин + одна строка FormKey: быстрый поиск по top-level группам; при enumerateFallback: true (по умолчанию) при промахе — полный перебор major-записей только этого файла (на больших мастерах вроде Skyrim.esm может занять заметное время). Ответ: found, lookupStrategy, formKey, recordType, editorId |
mutagen_plugin_master_closure | Предки по цепочке Master в заголовке TES4 (не обратные зависимости); аргументы: pluginFileNames[], maxNodes (по умолчанию 512, до 4096). Перед BFS выполняется пакетный резолв путей для всех включённых плагинов из plugins.txt (общий кэш с остальными инструментами). В ответе: nodes, unresolvedMasters, флаг truncated |
mutagen_verify_masters | По первым maxPlugins плагинам из plugins.txt (по умолчанию только включённые): чтение списков Master без полных заголовков; ответ — directEdges (опционально), missingMasters (notInLoadOrder / disabled / fileNotFound), orderViolations (мастер ниже зависимого), cycles (SCC по рёбрам «мастер раньше зависимого»), dependentFileMissing, readErrors, truncated если в списке остались необработанные включённые. includeDirectEdges: false убирает массив directEdges из JSON (счётчик остаётся в counts.directEdges) |
mutagen_formid_override_chain | FormKey (hexId:PluginFile.esp, как в Mutagen) → плагины из резолвящегося лоада (включённые строки plugins.txt с файлом на диске плюс при PrependImplicitStandardMasters стандартные мастера SSE в начале, если их не было в txt), в которых есть major-запись с точно таким ключом; победитель — макс. индекс. Строка должна совпадать с тем, что возвращает Mutagen (см. mutagen_plugin_lookup_form_key / mutagen_plugin_major_records_sample): для оверрайдов суффикс после : часто совпадает с файлом, где лежит строка, а не только с колонкой мастера в xEdit. Параметр accurateRecordLookup: при true на каждом плагине используется полный перебор major-записей вместо top-level ContainsKey — очень медленно на длинном лоаде, если ключа нет; только для отладки. При totalContributors: 0 в ответе (если удалось собрать статистику) есть emptyResultDiagnostics: счётчики включённых строк plugins.txt vs резолвнутых файлов, флаги стандартных мастеров (Skyrim.esm, Update.esm, …), текстовый hint. Остальное: winnerOnly, maxChainLength (обрезка JSON), overlay vs OverrideChainUseBinaryOverlay, pluginsScannedFromEnd. Пример: 000D62:AI Overhaul.esp |
mutagen_compare_two_plugins | Пересечение FormKey по major-записям в двух ESP/ESM; sameBinaryShape через Equals Mutagen. recordType — подстрока имени типа записи |
mutagen_plugin_record_flags_report | По одному плагину: удалённые записи (флаг Deleted через рефлексию) и кандидаты ITM (равенство записи победителю при лоаде без этого мода). Счётчик majorRecordsExamined ограничен maxRecords. includeItmCandidates: false сильно ускоряет (нет второго полного лоада и разрешения победителя по каждой записи) |
Ответы — JSON-текст. Для conflict-tools при ExposeToolTimingsInJson: true в том же JSON есть timings (мс) — удобно для агента и UI без stderr. У mutagen_formid_override_chain в overlay-режиме в timings есть resolvedListingsPrepMs (суммарно: чтение листингов + резолв путей до скана). Разбивка readListingsMs / resolvePathsMs выводится только в NDJSON-событии OverrideChainResolvedListings (см. ToolDiagnosticsLogPath), а не в поле timings JSON-ответа. Дополнительно логи в stderr: сборка GameEnvironment, RentFullEnvironment: CACHE HIT|MISS|…, те же цифры дублируются текстом. Если задан ToolDiagnosticsLogPath, события и тайминги пишутся в файл построчно (NDJSON) — удобно, когда stderr клиента MCP не сохраняется или нужен последующий разбор (jq, Grafana Loki и т.п.). В Docker смонтируйте каталог для файла, например -v "/path/to/mcp-logs:/logs" и -e MutagenMcp__ToolDiagnosticsLogPath=/logs/mutagen-mcp-diagnostics.ndjson.
Производительность тяжёлых инструментов
mutagen_formid_override_chain:winnerOnly: true— один победитель (макс. индекс лоадордера); overlay параллелит открытия как полный скан.winnerOnly: false— полная цепочка.accurateRecordLookup: true— перебор всех major-записей в каждом плагине лоада вместо быстрого top-level поиска: на сотнях модов и «левом» FormKey время может исчисляться минутами; по умолчаниюfalse. ВtimingsJSON (и NDJSONConflictToolOk) для overlay естьresolvedListingsPrepMs. Детальная разбивка чтения лоада vs резолва путей — в NDJSONOverrideChainResolvedListings(readListingsMs,resolvePathsMs).maxChainLengthтолько обрезка JSON.mutagen_verify_mastersсincludeDirectEdges: falseсильно уменьшает размер ответа при том же объёме работы на диске (чтение заголовков плагинов для графа).mutagen_list_plugins: для обзора лоада без гигантского JSON используйтеoffsetи умеренныйmaxPlugins; для поиска «дыр» в установке —onlyFileMissing.- Кэш резолва путей (см. выше) переиспользуется между вызовами в одном процессе: повторный
mutagen_list_plugins,mutagen_verify_masters,mutagen_formid_override_chainи т.д. после первого полного прохода по именам дают гораздо меньше обращений к диску, пока не сменились корни илиmodlist.txt. - Включите
CacheGameEnvironment(и при необходимостиEnvironmentCacheMaxAgeSeconds), если клиент MCP часто вызывает тяжёлые инструменты с полным лоадом подряд: повторно не поднимается полныйGameEnvironment(кроме сценария ITM — там по-прежнему строится второй лоад без целевого мода). - Для Docker задайте достаточный лимит памяти контейнера: кэш окружения держит один полный граф модов в RAM; словарь путей к плагинам обычно скромнее, но тоже живёт в процессе.
- Не запускайте несколько долгих вызовов параллельно на одном stdio-процессе без необходимости.
Ограничения v1
mutagen_formid_override_chainне видит плагины, включённые вplugins.txt, но без найденного файла подDataFolder/PluginSearchRoots/ MO2 — они выпадают из скана. Стандартные мастера (Skyrim.esm, …) приPrependImplicitStandardMasters: trueдобавляются к лоаду даже без строк вplugins.txt(см. конфиг). Пустая цепочка при корректном лоаде часто означает неверную строку FormKey относительно Mutagen, а не только «нет оверрайдов».- Инструменты по FormKey и сравнению плагинов используют резолв путей к файлам плагинов (внутрипроцессный кэш имён до смены корней /
modlist.txt/ProbeDataSubfolder). ПолныйGameEnvironmentподнимается в зависимости от режима (overlay vs полный лоад) иCacheGameEnvironment; держите лимиты (maxRecords,maxResults, настройкиMax*) разумными. - Семантика ITM / сравнение записей опирается на Loqui/Mutagen
Equals, а не на побайтовое совпадение с xEdit; удалённые записи определяются черезMajorRecordFlagsрефлексией — возможны расхождения с UI. includeHeadersвmutagen_list_pluginsоткрывает каждый плагин через Mutagen — держитеmaxPluginsумеренным; для длинного лоада используйтеoffsetи при необходимостиonlyFileMissing; большие JSON-ответы клиент MCP может обрывать по таймауту.mutagen_plugin_master_closureстроит только мастер → мастер мастера из заголовков; патчи без записи в мастерах и «кто зависит от плагина X» сюда не входят.mutagen_verify_mastersпо умолчанию обрабатывает только первыеmaxPluginsвключённых записей в порядке файла — не весь список целиком; увеличьтеmaxPluginsилиMaxPluginsPerRequestдля полного прохода.- Разрешение по
modlist.txtопирается на строки+ИмяПапкиМода; при несовпадении имени папки на диске плагин может не находиться.
Лицензия
Укажите лицензию по желанию владельца репозитория (файл не добавлялся автоматически).
