CurseForge ARK MCP Server
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 …) указывает на него.
Что он делает и чего он намеренно не может делать
Семь инструментов, все только для чтения:
Инструмент | Ответы |
| "Какие моды ASA соответствуют этому запросу?" |
| "Что такое проект 777001?" |
| "Какие файлы опубликовал этот мод?" |
| "Что это за конкретный файл?" |
| "Есть ли более новый файл для этого мода, чем тот, который я запускаю?" |
| "Что тянет за собой этот мод?" (пакетно, один запрос на уровень дерева) |
| "Это я, ключ или 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 |
|
| разрешение game-id, |
E2 |
|
|
|
E3 |
|
|
|
E4 |
|
|
|
E5 |
|
|
|
E6 |
|
|
|
E7 |
|
|
|
Механически:
Совпадает совместно по
{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 | Значение | Необнаружимо без ключа (§5) | Неверное значение возвращает чистые, пустые, ошибочные результаты поиска |
U2 | Видна ли ASA предоставленному ключу вообще | Необнаружимо без ключа | Может полностью заблокировать v1 |
U3 | Поля | Опубликованная схема | Каждый вывод инструмента |
U4 | Поля | Опубликованная схема |
|
U5 |
| Опубликованная схема | Обход |
U6 | Сопоставление числового перечисления | НЕ РАЗРЕШЕНО. Три попытки по документации; страница показывает | Определяет, является ли ребро обязательным, опциональным, инструментом или несовместимым — т.е. следует ли за ним вообще. |
U7 | Числовое перечисление | Не разрешено со страницы документации. Частичное подтверждение только: Upload API использует имена | Фильтрация |
U8 | Присутствует ли | Документированная форма; никогда не наблюдалась | Этот клиент выдает ошибку, а не предполагает одну страницу |
U9 | Заполняют ли моды ASA на самом деле | Схема говорит, что могут; поведение, специфичное для ASA, неизвестно | Всегда пустое поле — это пробел в возможностях, а не ошибка — и правило трех состояний требует их различать |
U10 | Любое ограничение на количество идентификаторов в теле | Не документировано. Ограничение в 200 идентификаторов в этом клиенте — наше, не вендора | Стратегия разбиения на части |
U11 | Лимиты скорости CurseForge | Не документировано. Опубликованных цифр не найдено |
|
U12 | Реальное поведение пагинации после | Документировано только ограничение | Раскрытие усечения в §4.3 |
U13 | Базовый URL | Получено из документации | От этого зависит привязка хоста |
Два следствия, которые вы увидите в выводе инструментов
relationType и releaseType отображаются как сырые целые числа и никогда не сопоставляются. Не с
required/optional, не с release/beta/alpha. CurseForge не публикует таблицу значений ни для одного из них,
и неправильная метка привела бы к списку зависимостей — или рекомендации по обновлению — которые
были бы неверны так, что никто бы этого не проверил. Поэтому resolve_mod_dependencies следует каждому
ребру и сообщает об этом: он собирает лишнее, и его вывод прямо об этом говорит. Широкая сеть, по крайней мере,
видимо широка.
get_latest_file требует, чтобы вы сказали, что значит «последний». Самый новый по fileDate,
самый новый, соответствующий версии игры, и самый новый с заданным releaseType дают разные ответы, и
решение об обновлении мода, принятое на основе неправильного, — это именно тот класс уверенно-неправильных ответов, против которого
настроен этот репозиторий. selection не имеет значения по умолчанию:
| Также требует | Означает |
| — | Самый новый из всех файлов-кандидатов по |
|
| Самый новый файл, объявляющий эту версию игры |
|
| Самый новый файл с этим целым числом типа релиза |
Нет именованного фильтра 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 enumeratorsrc/buildinfo.ts генерируется и игнорируется git, помечается коммитом и флагом dirty перед каждым запуском tsc и выводится через get_api_diagnostics. dist/ игнорируется git, и сервер запускается из него как долгоживущий процесс, поэтому «какой код создал этот ответ?» нельзя ответить из git во время выполнения — он должен путешествовать с артефактом.
Отклонения от родственного репозитория, указанные намеренно
Открытые вопросы 7 и 8 ADR-002 требуют их упоминания там, где они происходят:
Та же базовая линия, намеренно. Node ≥20, TypeScript 5.9.3,
@modelcontextprotocol/sdk1.30.0,zod4.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». Модель содержит оба. Ни один сервер не вызывает другой, и ни один никогда не хранит учётные данные другого.
This server cannot be installed
Maintenance
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.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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