Skip to main content
Glama

curseforge-ark-mcp

A read-only MCP server for CurseForge mod curation, discovery, and update surveillance for ARK: Survival Ascended.

ЭТО v0. НИЧЕГО ЗДЕСЬ НЕ БЫЛО ПРОВЕРЕНО НА ЖИВОМ ОТВЕТЕ.

Ключа API CurseForge пока нет. Ключ не выдается самостоятельно — он предоставляется по заявке в Overwolf — поэтому ни один аутентифицированный вызов никогда не был сделан из этого репозитория, никем и ни в какой момент. Каждый путь поля в каждом фикстуре и каждом выводе инструмента — это гипотеза, прочитанная из опубликованной схемы.

Это не скромность. Родственный репозиторий nitrado-ark-mcp строил свои фикстуры таким же тщательным образом на основе документации, и коммит 5481c04 там исправил три пути полей, которые были неверны, пока не были проверены на живых ответах. Предположите, что в этом репозитории есть свои три ожидающих.

Номер версии — 0.1.0, и это утверждение о статусе проверки. В этом README нет раздела "Проверено на живом аккаунте", и его отсутствие является точным, а не упущением.

Что проверено сегодня — это поведение самого репозитория: список разрешённых конечных точек, привязка хоста, нормализация путей, границы пагинации, обработка обёртки и дисциплина трёх состояний отсутствует/пусто/неизвестно. Всё это протестировано на поддельном fetch, без ключа и без сети. 146 тестов, 0 ошибок на момент написания.

Запись проектного решения: docs/adr/ADR-002-endpoint-allow-list.md (статус: ПРЕДЛОЖЕНО). Каждая ссылка на раздел ниже (§1, §4.3, §14.3 …) указывает на него.


Что он делает и чего он намеренно не может делать

Семь инструментов, все только для чтения:

Инструмент

Ответы

search_mods

"Какие моды ASA соответствуют этому запросу?"

get_mod

"Что такое проект 777001?"

list_mod_files

"Какие файлы опубликовал этот мод?"

get_mod_file

"Что это за конкретный файл?"

get_latest_file

"Есть ли более новый файл для этого мода, чем тот, который я запускаю?"

resolve_mod_dependencies

"Что тянет за собой этот мод?" (пакетно, один запрос на уровень дерева)

get_api_diagnostics

"Это я, ключ или CurseForge?" — и "насколько честна эта сборка?"

Он не может:

  • Загружать или устанавливать что-либо. GET /v1/mods/{modId}/files/{fileId}/download-url — это документированное чтение, на привязанном хосте, и оно отклонено — потому что его нет в списке разрешённых конечных точек (DEC-002 §11.3). Nitrado устанавливает моды сам.

  • Писать что-либо, куда-либо. Ни одна запись списка разрешённых не называет изменяющий эндпоинт. CurseForge управляет изменяющим API загрузки на другом хосте (§14.2); привязка хоста отклоняет его второй раз по независимой причине.

  • Публиковать или создавать мод. Отклонено прямо (DEC-002 Постановление 2). Обеспечивается утверждением при загрузке, а не обещанием: регистрация инструмента, объявляющего что-либо, кроме уровня 1, заставляет процесс отказаться запускаться.

  • Касаться Nitrado. В конфигурационной поверхности этого репозитория нет ни одной переменной NITRADO_*, и её отсутствие является контролем. Этот сервер не содержит токена Nitrado и не читает конфигурацию Nitrado.

  • Просыпаться по таймеру и обновлять ваш сервер. Нет планировщика, нет цикла опроса, нет сохранённого состояния "последняя увиденная версия" (§10). Наблюдение означает, что модель может увидеть новую версию. Она не имеет права действовать.


Узкое место: список разрешённых конечных точек, а не проверка метода

Это единственное проектное решение, которое стоит прочитать перед тем, как трогать код.

CurseForge использует POST для ЧТЕНИЯ. POST /v1/mods и POST /v1/mods/files — это массовые извлечения, и именно они делают resolve_mod_dependencies стоимостью один запрос на уровень зависимостей вместо одного на узел. Так что отказ родственного репозитория method !== "GET" → refuse здесь провалился бы самым дорогим образом: он бы сработал. Он бы отказывал в чём-то, проходил свои собственные тесты и тихо делал бы сервер плохим в своей работе.

И очевидное исправление хуже, чем ошибка:

allowed = { GET }          → the batch reads are refused (broken, loudly)
allowed = { GET, POST }    → every request this client can construct is allowed

Документированный каталоговый API содержит только GET и POST. Шлюз, пропускающий оба, пропускает всё — продолжая выглядеть прилично.

Поэтому вместо этого каждый исходящий запрос должен соответствовать явной записи в закрытом списке пар {method, path}. Семь записей, в src/allowlist.ts:

#

Метод

Путь

Служит

E1

GET

/v1/games

разрешение game-id, get_api_diagnostics

E2

GET

/v1/mods/search

search_mods

E3

GET

/v1/mods/{modId}

get_mod, get_latest_file

E4

GET

/v1/mods/{modId}/files

list_mod_files, get_latest_file

E5

GET

/v1/mods/{modId}/files/{fileId}

get_mod_file

E6

POST

/v1/mods

resolve_mod_dependencies (массовое чтение)

E7

POST

/v1/mods/files

resolve_mod_dependencies (массовое чтение)

Механически:

  • Совпадает совместно по {method, path}. E3 не разрешает DELETE /v1/mods/123. E6 не разрешает POST /v1/mods/123.

  • Хост привязан к https://api.curseforge.com, и привязка — это разрешение одного источника, а не запрет какого-либо другого.

  • Сегменты id привязываются к [0-9]+, а не к [^/]+. Это существенно: нестрогий {modId} заставляет E3 поглотить /v1/mods/search. Числовая привязка делает эту неоднозначность структурно невозможной, а не зависимой от порядка совпадения — и есть тест, который переворачивает весь список, чтобы доказать, что порядок не спасает.

  • Одна нормализация, перед проверкой, и URL строится из её вывода. Один раз декодировать проценты; отказать в любом %, который выжил; свернуть обратные слеши; отказать в сегментах ., .. и пустых.

  • Только E6/E7 могут нести тело, форма которого проверяется перед отправкой. Тело на записи GET отклоняется, а не игнорируется.

  • Только resolve_mod_dependencies может достигать записи POST (§8), что обеспечивается на транспортном уровне.

Режим отказа — "несовпавший запрос отклонён", никогда не "неопознанный запрос отправлен." И добавление возможности — это проверяемая разница в одну строку, вопрос для проверки которой — "является ли этот эндпоинт чтением?" — человек может ответить на него.

Тест, доказывающий, что это список разрешённых

GET /v1/mods/{modId}/files/{fileId}/download-url отклонён. Это документированное чтение, GET, на привязанном хосте, с правильно сформированными числовыми id. Он отклонён исключительно потому, что его нет в списке. Если этот тест когда-либо проходит по другой причине — отказ по привязке хоста, отказ по пути — свойство не реализовано, поэтому тест проверяет код и детали отказа, а не просто то, что что-то выбросило исключение.

Каждый тест на отказ также проверяет количество вызовов поддельного fetch, потому что "отклонено до того, как запрос построен" — это фактическое условие, и ошибка, выброшенная после отправки, удовлетворяла бы более слабому утверждению. И набор тестов на отказ предваряется тестом прообраза, доказывающим, что все семь записей действительно отправляют запросы — набор тестов на отказ для клиента, который не может ничего отправить, проходит отлично и ничего не доказывает.


Всё ещё не проверено

Каждая строка ниже — это ГИПОТЕЗА. Это §14.3 ADR-002, воспроизведённый полностью. Пути полей прочитаны из опубликованных схем, что является именно тем классом артефактов, который породил три неверных пути в родственном репозитории.

#

Утверждение

Основание

Почему это важно

U1

Значение gameId ASA

Необнаружимо без ключа (§5)

Неверное значение возвращает чистые, пустые, ошибочные результаты поиска

U2

Видна ли ASA предоставленному ключу вообще

Необнаружимо без ключа

Может полностью заблокировать v1

U3

Поля Mod: id, gameId, name, slug, latestFiles, latestFilesIndexes, dateModified, links, categories, allowModDistribution

Опубликованная схема

Каждый вывод инструмента

U4

Поля File: id, modId, displayName, fileName, fileDate, gameVersions, sortableGameVersions, dependencies, releaseType, isAvailable

Опубликованная схема

get_latest_file, list_mod_files

U5

FileDependency = { modId, relationType }

Опубликованная схема

Обход resolve_mod_dependencies

U6

Сопоставление числового перечисления FileRelationType

НЕ РАЗРЕШЕНО. Три попытки по документации; страница показывает relationType как голое целое число без опубликованной таблицы значений. Не берите сопоставление из памяти, блога или этого репозитория.

Определяет, является ли ребро обязательным, опциональным, инструментом или несовместимым — т.е. следует ли за ним вообще. resolve_mod_dependencies блокируется на этом.

U7

Числовое перечисление FileReleaseType (release/beta/alpha)

Не разрешено со страницы документации. Частичное подтверждение только: Upload API использует имена alpha, beta, release — что подтверждает набор, но не числовое сопоставление в read API.

Фильтрация get_latest_file; обработка alpha как release — это неверная рекомендация по обновлению

U8

Присутствует ли pagination на каждой конечной точке с пагинацией

Документированная форма; никогда не наблюдалась

Этот клиент выдает ошибку, а не предполагает одну страницу

U9

Заполняют ли моды ASA на самом деле dependencies, sortableGameVersions, latestFilesIndexes

Схема говорит, что могут; поведение, специфичное для ASA, неизвестно

Всегда пустое поле — это пробел в возможностях, а не ошибка — и правило трех состояний требует их различать

U10

Любое ограничение на количество идентификаторов в теле POST /v1/mods / POST /v1/mods/files

Не документировано. Ограничение в 200 идентификаторов в этом клиенте — наше, не вендора

Стратегия разбиения на части

U11

Лимиты скорости CurseForge

Не документировано. Опубликованных цифр не найдено

get_api_diagnostics сообщает наблюдаемые заголовки или null, никогда не догадку

U12

Реальное поведение пагинации после index 0 и поведение при достижении предела в 10000

Документировано только ограничение

Раскрытие усечения в §4.3

U13

Базовый URL https://api.curseforge.com

Получено из документации

От этого зависит привязка хоста

Два следствия, которые вы увидите в выводе инструментов

relationType и releaseType отображаются как сырые целые числа и никогда не сопоставляются. Не с required/optional, не с release/beta/alpha. CurseForge не публикует таблицу значений ни для одного из них, и неправильная метка привела бы к списку зависимостей — или рекомендации по обновлению — которые были бы неверны так, что никто бы этого не проверил. Поэтому resolve_mod_dependencies следует каждому ребру и сообщает об этом: он собирает лишнее, и его вывод прямо об этом говорит. Широкая сеть, по крайней мере, видимо широка.

get_latest_file требует, чтобы вы сказали, что значит «последний». Самый новый по fileDate, самый новый, соответствующий версии игры, и самый новый с заданным releaseType дают разные ответы, и решение об обновлении мода, принятое на основе неправильного, — это именно тот класс уверенно-неправильных ответов, против которого настроен этот репозиторий. selection не имеет значения по умолчанию:

selection

Также требует

Означает

newest_by_file_date

Самый новый из всех файлов-кандидатов по fileDate

newest_matching_game_version

game_version

Самый новый файл, объявляющий эту версию игры

newest_with_release_type

release_type (сырое целое)

Самый новый файл с этим целым числом типа релиза

Нет именованного фильтра release/beta/alpha, потому что U7 не разрешен, и этот сервер не будет изобретать сопоставление. Вы передаете целое число, которое имеете в виду.

Это определение — ОТКРЫТЫЙ ВОПРОС ПРОДУКТА. Открытый вопрос 2 ADR-002 помечает его как решение основателя, которое не было принято, когда это было построено, поэтому инструмент параметризован, а не догматичен: когда ответ поступит, он станет значением по умолчанию или одним вариантом меньше — небольшим изменением, а не переписыванием. Каждый ответ повторяет использованный порядок, по чему он фильтровал, сколько кандидатов он рассмотрел и откуда взялись кандидаты.


Настройка

Node 20+ (разработано на 22). Этап сборки настраивать не нужно; npm test сначала собирает.

npm install
npm test          # builds, then runs the suite — no key, no network
npm run typecheck
npm run smoke     # refuses cleanly until a key exists, naming what it would probe

Затем, когда у вас есть ключ:

cp .env.example .env
# set CURSEFORGE_API_KEY, then:
npm run smoke

Конфигурация клиента MCP (stdio):

{
  "mcpServers": {
    "curseforge-ark": {
      "command": "node",
      "args": ["C:/path/to/curseforge-ark-mcp/dist/src/server.js"],
      "env": { "CURSEFORGE_API_KEY": "your-key" }
    }
  }
}

Сервер отказывается запускаться без ключа, называя оба местоположения, которые он проверил, точную переменную и тот факт, что ключ не является самообслуживаемым. Сервер MCP stdio, который запускается чисто, а затем выдает ошибку во всех семи инструментах, — это ужасная вещь для отладки.

О ключе

Ключ API отправляется как заголовок запроса x-api-key. Это не токен Authorization: Bearer — это схема родственного сервера Nitrado, и этот репозиторий намеренно не поддерживает оба варианта, потому что поддержка обоих означала бы, что этот код может передавать учетные данные в форме, которую CurseForge никогда не документировал.

Ключ предоставляется приложению Overwolf и не подлежит передаче. Практическое следствие и единственная причина существования этого абзаца: утечка означает отзыв и повторную подачу заявки, а повторная подача заявки — это очередь, а не сброс самообслуживания. Вы не можете восстановить его за чашкой кофе, и вы не можете одолжить чужой. Относитесь к нему соответственно — .env игнорируется git, .env.example несет имя переменной и пустое значение, и ни одно значение ключа не появляется ни в одном зафиксированном файле.

В этом репозитории нет матрицы областей, и это не упущение: CurseForge не публикует область только для чтения и не предлагает выбора области, поэтому матрице не из чего состоять. Свойство только для чтения этого сервера исходит из его собственного белого списка конечных точек, а не из более узких учетных данных. Также нет инструкции по утечке токена — утекший ключ предоставляет доступ для чтения к публичному каталогу плюс потребление квоты, что реально и не относится к той же категории, что токен Nitrado из родственного репозитория (документированный как эквивалент полного контроля над игровым сервером). Это правильное масштабирование обсуждается в ADR-002 §12, и оно основано на одном утверждении, изложенном там, чтобы его можно было опровергнуть: данные каталога CurseForge являются публичными по построению.

Редактирование, всё целиком

Одно правило: никогда не повторяйте ключ API. Одна функция, src/scrub.ts, применяется к сообщениям об ошибках и к любому фрагменту тела запроса. Заголовки запросов никогда не появляются в ошибках — ни ключ, ни отредактированный ключ, ни список имён заголовков. get_api_diagnostics сообщает, настроен ли ключ, и никогда его значение, префикс или длину.


Поведение, которое стоит знать перед чтением вывода

  • Пустое не означает неизвестное. data: [] означает, что CurseForge ответил «ничего» — реальный ответ, с повторённым запросом, чтобы вы могли видеть, что ничего не вернулось. Отсутствующее поле равно null, никогда 0, "" или []. Запрос, который не завершился, или ответ с неправильной структурой — это ошибка, а не значение.

  • Отсутствующий ключ data — это ошибка, а не пустой результат. Приведение его к [] превратило бы сломанную интеграцию в «результаты не найдены».

  • Отсутствующая pagination на страничном конечном точке — тоже ошибка. Предположение одной страницы — это как инструмент сообщает 50 из 900 модов, как будто это все (U8 — именно этот открытый вопрос).

  • pageSize > 50 отклоняется, а не обрезается, и то же самое для index + pageSize > 10000 — с указанием наибольшего допустимого размера страницы при этом индексе в сообщении. Модель, которая запрашивает 200 и молча получает 50, будет рассуждать о странице, как если бы это был набор.

  • Когда totalCount превышает 10000, вывод инструмента сообщает, что хвост НЕДОСТУПЕН, этими словами, и советует сузить фильтр, а не пролистывать.

  • gameId для ASA определяется во время выполнения из GET /v1/games и кэшируется на всё время жизни процесса; он никогда не жёстко закодирован и никогда не угадывается. Если его невозможно разрешить, сервер громко падает, указывая, что искал и сколько игр мог видеть ключ — потому что gameId является обязательным фильтром поиска, поэтому неправильный возвращает чистые, пустые, совершенно неправильные результаты вместо ошибки. Установите CURSEFORGE_GAME_SLUG, если встроенные кандидаты окажутся неверными.

  • resolve_mod_dependencies ограничен глубиной 4 и 400 узлами, с посещённым набором для циклов. При достижении предела результат сообщается как усечённый, этим словом, с перечислением неизведанной границы.


Структура репозитория

src/
  allowlist.ts    THE CHOKEPOINT — seven entries, host pin, normalization, bounds, body checks
  client.ts       the single transport; the ONLY place x-api-key is attached; envelope unwrap
  config.ts       refuse-to-start; no NITRADO_*, no mode switch, no settable base URL
  coerce.ts       empty / absent / unknown, kept apart
  errors.ts       the error taxonomy
  game.ts         runtime gameId resolution (injected, process-lifetime cache)
  registry.ts     ToolDef + tier, and the boot assertion that refuses a non-tier-1 tool
  scrub.ts        never echo the key. That is the whole module.
  probe-plan.ts   one probe per unverified row, asserted complete by a test
  server.ts       stdio entry point
  smoke.ts        the key-arrival command
  tools/          the seven tools
test/             146 tests; fixtures are synthetic in content, structural in shape
scripts/          buildinfo generator, test enumerator

src/buildinfo.ts генерируется и игнорируется git, помечается коммитом и флагом dirty перед каждым запуском tsc и выводится через get_api_diagnostics. dist/ игнорируется git, и сервер запускается из него как долгоживущий процесс, поэтому «какой код создал этот ответ?» нельзя ответить из git во время выполнения — он должен путешествовать с артефактом.

Отклонения от родственного репозитория, указанные намеренно

Открытые вопросы 7 и 8 ADR-002 требуют их упоминания там, где они происходят:

  • Та же базовая линия, намеренно. Node ≥20, TypeScript 5.9.3, @modelcontextprotocol/sdk 1.30.0, zod 4.4.3, node:test через тот же перечислитель scripts/run-tests.mjs. Тот же рецензент, те же идиомы, меньшая стоимость чтения обоих.

  • @cfworker/json-schema не является зависимостью здесь. Он поддерживает проверку cron-выражений в родственном проекте, и здесь нет пути записи для проверки.

  • registry.ts портирован по структуре и сохраняет tier, но отбрасывает механизм режима/списка разрешённых — ему нечего было бы фильтровать, поскольку каждый инструмент является уровнем 1, а каждая конечная точка — чтением. Переменная режима без ничего за ней рекламирует управление, которого не существует. Одна проверка при запуске из пяти строк заменяет подсистему.

  • redact.ts не портирован (§12.1). См. «Редактирование, всё целиком» выше.

  • Нет кода ошибки UNKNOWN_OUTCOME. Родственному проекту он нужен, потому что потерянный ответ на PUT всё ещё мог изменить мир. Каждый запрос, который может выполнить этот клиент, — это чтение, поэтому тайм-аут действительно означает «этого не произошло», и повтор безопасен.

  • npm run smoke завершается с кодом 0, когда отказывается из-за отсутствия ключа. Отказ — это ожидаемый результат его запуска сегодня, и баннер говорит SMOKE NOT RUN безошибочно. Если вы хотите, чтобы конвейер завершался ошибкой при отсутствии ключа, привяжите конвейер к ключу, а не к этому коду выхода.


Связанные записи

В родственном репозитории nitrado-ark-mcp, только для чтения отсюда — ничто в этом репозитории не было изменено данным:

  • docs/decisions/EXECUTIVE-BOARD-2026-08-16-curseforge-mods.md — протокол заседания (DEC-002), который выполняет этот репозиторий. Решения Председателя обязательны.

  • docs/decisions/decision-log.md — DEC-002, и DEC-001 для разделения области, на котором основан §10.

  • docs/adr/ADR-001-write-path-enforcement.md — форма, которую портирует ADR-002, и источник правила нормализации, обоснования проверки при запуске и обоснования отказа запуска.

Два сервера остаются независимыми. nitrado-ark-mcp отвечает «эти идентификаторы проектов находятся в active-mods»; этот репозиторий отвечает «самый новый файл проекта X — это v2.1». Модель содержит оба. Ни один сервер не вызывает другой, и ни один никогда не хранит учётные данные другого.

-
license - not tested
-
quality - not tested
C
maintenance

Maintenance

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

Related MCP Connectors

  • A MCP server built for developers enabling Git based project management with project and personal…

  • MCP server for doc2mcp documentation, generated by doc2mcp.

  • Official MCP server for Lovable, the AI-powered full-stack app builder.

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/JShort-bufr/curseforge-ark-mcp'

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