Skip to main content
Glama

curseforge-ark-mcp

Un servidor MCP de solo lectura para la curación, descubrimiento y vigilancia de actualizaciones de mods de CurseForge para ARK: Survival Ascended.

ESTO ES v0. NADA DE ESTO HA SIDO VERIFICADO CONTRA UNA RESPUESTA EN VIVO.

Todavía no existe ninguna clave API de CurseForge. La clave no es autoservicio — se concede mediante solicitud a Overwolf — por lo que nunca se ha realizado ninguna llamada autenticada desde este repositorio, por nadie, en ningún momento. Cada ruta de campo en cada fixture y en cada salida de herramienta es una hipótesis leída de un esquema publicado.

Esto no es modestia. El repositorio hermano nitrado-ark-mcp construyó sus fixtures de la misma manera cuidadosa a partir de la documentación, y el commit 5481c04 allí corrigió tres rutas de campo que estaban equivocadas hasta que se verificaron contra respuestas en vivo. Asuma que este repositorio tiene tres de las suyas esperando.

El número de versión es 0.1.0 y es una afirmación sobre el estado de verificación. No hay ninguna sección "Verificado contra la cuenta en vivo" en este README, y su ausencia es precisa más que una omisión.

Lo que está verificado hoy es el comportamiento de este repositorio: la lista blanca de endpoints, el pin del host, la normalización de rutas, los límites de paginación, el manejo de envoltorios y la disciplina de tres estados ausente/vacío/desconocido. Todo está probado contra un fetch falso inyectado, sin clave y sin red. 146 pruebas, 0 fallos en el momento de escribir esto.

Registro de diseño: docs/adr/ADR-002-endpoint-allow-list.md (estado: PROPUESTO). Cada referencia a una sección a continuación (§1, §4.3, §14.3 …) apunta a él.


Qué hace, y qué deliberadamente no puede hacer

Siete herramientas, todas de solo lectura:

Herramienta

Responde

search_mods

"¿Qué mods de ASA coinciden con este término?"

get_mod

"¿Qué es el proyecto 777001?"

list_mod_files

"¿Qué archivos ha publicado este mod?"

get_mod_file

"¿Qué es este archivo específico?"

get_latest_file

"¿Hay un archivo más reciente para este mod que el que estoy ejecutando?"

resolve_mod_dependencies

"¿Qué requiere este mod?" (por lotes, una solicitud por nivel de árbol)

get_api_diagnostics

"¿Soy yo, la clave o CurseForge?" — y "¿qué tan honesta es esta compilación?"

No puede:

  • Descargar ni instalar nada. GET /v1/mods/{modId}/files/{fileId}/download-url es una lectura documentada, en el host fijado, y está rechazada — porque no está en la lista blanca de endpoints (DEC-002 §11.3). Nitrado instala los mods por sí mismo.

  • Escribir nada, en ningún sitio. Ninguna entrada de la lista blanca nombra un endpoint mutante. CurseForge sí opera una API de carga mutante en un host diferente (§14.2); el pin del host la rechaza una segunda vez por una razón independiente.

  • Publicar o crear un mod. Rechazado de plano (DEC-002 Fallo 2). Aplicado mediante una aserción de arranque, no por una promesa: registrar una herramienta que declare algo que no sea el nivel 1 hace que el proceso se niegue a iniciar.

  • Tocar Nitrado. No existe ninguna variable NITRADO_* en la superficie de configuración de este repositorio, y su ausencia es un control. Este servidor no tiene ningún token de Nitrado y no lee ninguna configuración de Nitrado.

  • Despertarse con un temporizador y actualizar su servidor. Sin programador, sin bucle de sondeo, sin estado persistido de "última versión vista" (§10). La vigilancia significa que el modelo puede observar una nueva versión. No puede actuar.


El punto de estrangulamiento: una lista blanca de endpoints, no una verificación de método

Esta es la única decisión de diseño que vale la pena leer antes de tocar el código.

CurseForge usa POST para LEER. POST /v1/mods y POST /v1/mods/files son recuperaciones masivas, y son lo que hace que resolve_mod_dependencies cueste una solicitud por nivel de dependencia en lugar de una por nodo. Así que el method !== "GET" → refuse del repositorio hermano fallaría aquí de la manera más costosa posible: funcionaría. Rechazaría cosas, pasaría sus propias pruebas y silenciosamente haría que el servidor fuera malo en su trabajo.

Y la solución obvia es peor que el error:

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

La API de catálogo documentada contiene solo GET y POST. Una puerta que admite ambos admite todo — mientras sigue pareciendo presente.

Así que en su lugar, cada solicitud saliente debe coincidir con una entrada explícita en una lista cerrada de pares {method, path}. Siete entradas, en src/allowlist.ts:

#

Método

Ruta

Sirve

E1

GET

/v1/games

resolución de ID de juego, 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 (lectura masiva)

E7

POST

/v1/mods/files

resolve_mod_dependencies (lectura masiva)

Mecánicamente:

  • Coincidido en {method, path} conjuntamente. E3 no autoriza DELETE /v1/mods/123. E6 no autoriza POST /v1/mods/123.

  • El host está fijado a https://api.curseforge.com, y el pin es una autorización de un origen en lugar de una denegación de cualquier otro nombrado.

  • Los segmentos de ID se vinculan a [0-9]+, no a [^/]+. Esto es crucial: un {modId} permisivo hace que E3 se trague /v1/mods/search. El vínculo numérico hace que esa ambigüedad sea estructuralmente imposible en lugar de depender del orden de coincidencia — y hay una prueba que invierte toda la lista para demostrar que el orden no es lo que la salva.

  • Una normalización, antes de la verificación, y la URL se construye a partir de su salida. Decodificar porcentajes una vez; rechazar cualquier % que sobreviva; doblar barras invertidas; rechazar segmentos ., .. y vacíos.

  • Solo E6/E7 pueden llevar un cuerpo, verificado en forma antes del envío. Un cuerpo en una entrada GET se rechaza, no se descarta.

  • Solo resolve_mod_dependencies puede alcanzar una entrada POST (§8), aplicado en el transporte.

El modo de fallo es "solicitud no coincidente rechazada", nunca "solicitud no reconocida enviada". Y agregar una capacidad es un diff de una línea revisable cuya pregunta de revisión — "¿este endpoint es una lectura?" — es una que un humano puede realmente responder.

La prueba que demuestra que es una lista blanca

GET /v1/mods/{modId}/files/{fileId}/download-url está rechazada. Es una lectura documentada, un GET, en el host fijado, con ids numéricos bien formados. Está rechazada puramente porque no está en la lista. Si esa prueba alguna vez pasa por alguna otra razón — un rechazo por pin del host, un rechazo por ruta — la propiedad no está implementada, por lo que la prueba afirma el código y detalle del rechazo, no meramente que algo lanzó una excepción.

Cada prueba de rechazo también afirma el recuento de llamadas del fetch falso, porque "rechazado antes de que la solicitud se construya" es la disposición real, y un error lanzado después del envío satisfaría una aserción más débil. Y el conjunto de rechazos está precedido por una prueba de preimagen que demuestra que las siete entradas sí realizan el envío — un conjunto de rechazos sobre un cliente que no puede enviar nada pasa perfectamente y no prueba nada.


Aún no verificado

Cada fila a continuación es una HIPÓTESIS. Estas son §14.3 de ADR-002, reproducidas en su totalidad. Las rutas de campo se leen de esquemas publicados, que es exactamente la clase de artefacto que produjo tres rutas erróneas en el repositorio hermano.

#

Afirmación

Base

Por qué es importante

U1

El valor gameId de ASA

Indescubrible sin la clave (§5)

Un valor incorrecto devuelve resultados de búsqueda limpios, vacíos y erróneos

U2

Si ASA es visible para la clave otorgada

Indescubrible sin la clave

Podría bloquear v1 por completo

U3

Campos de Mod: id, gameId, name, slug, latestFiles, latestFilesIndexes, dateModified, links, categories, allowModDistribution

Esquema publicado

Cada salida de herramienta

U4

Campos de File: id, modId, displayName, fileName, fileDate, gameVersions, sortableGameVersions, dependencies, releaseType, isAvailable

Esquema publicado

get_latest_file, list_mod_files

U5

FileDependency = { modId, relationType }

Esquema publicado

Recorrido de resolve_mod_dependencies

U6

El mapeo de enumeración numérica de FileRelationType

NO RESUELTO. Tres intentos contra la documentación; la página muestra relationType como un entero simple sin una tabla de valores publicada. No tome un mapeo de memoria, de un blog o de este repositorio.

Determina si un borde es requerido, opcional, una herramienta o incompatible, es decir, si se sigue o no. resolve_mod_dependencies se bloquea en esto.

U7

La enumeración numérica de FileReleaseType (release/beta/alpha)

No resuelto desde la página de documentación. Corroboración parcial solamente: la API de carga utiliza los nombres alpha, beta, release — lo que respalda el conjunto, no el mapeo numérico en la API de lectura.

Filtrado de get_latest_file; tratar alpha como release es una recomendación de actualización incorrecta

U8

Si pagination está presente en cada endpoint paginado

Forma documentada; nunca observada

Este cliente genera un error en lugar de asumir una página

U9

Si los mods de ASA realmente completan dependencies, sortableGameVersions, latestFilesIndexes

El esquema dice que pueden; el comportamiento específico de ASA es desconocido

Un campo siempre vacío es una brecha de capacidad, no un error — y la regla de tres estados requiere distinguirlos

U10

Cualquier límite de recuento de ID en los cuerpos de POST /v1/mods / POST /v1/mods/files

No documentado. El límite de 200 ID en este cliente es nuestro, no del proveedor

Estrategia de fragmentación

U11

Límites de tasa de CurseForge

No documentado. No se encontró ninguna cifra publicada

get_api_diagnostics informa los encabezados observados o null, nunca una suposición

U12

Comportamiento real de paginación más allá del index 0, y comportamiento en el límite de 10000

Solo restricción documentada

La divulgación de truncamiento en §4.3

U13

URL base https://api.curseforge.com

Derivado de la documentación

El pin del host depende de ello

Dos consecuencias que verá en la salida de la herramienta

relationType y releaseType se muestran como enteros simples y nunca se mapean. No a required/optional, no a release/beta/alpha. CurseForge no publica una tabla de valores para ninguno de los dos, y una etiqueta incorrecta produciría una lista de dependencias — o una recomendación de actualización — que es incorrecta de una manera que nadie verificaría. Por lo tanto, resolve_mod_dependencies sigue cada borde y lo dice: sobre-recopila, y su salida lo indica claramente. Una red amplia es al menos visiblemente amplia.

get_latest_file requiere que usted diga qué significa "más reciente". El más reciente por fileDate, el más reciente que coincida con una versión del juego, y el más reciente con un releaseType determinado dan respuestas diferentes, y una decisión de actualización de mod basada en la incorrecta es exactamente la clase de respuesta segura pero incorrecta contra la que está organizado este repositorio. selection no tiene valor predeterminado:

selection

También requiere

Significa

newest_by_file_date

El más reciente de todos los archivos candidatos, por fileDate

newest_matching_game_version

game_version

El archivo más reciente que declara esa versión del juego

newest_with_release_type

release_type (un entero simple)

El archivo más reciente que lleva ese entero de tipo de release

No hay un filtro con nombre release/beta/alpha, porque U7 no está resuelto y este servidor no inventará el mapeo. Usted pasa el entero que quiere.

Esta definición es UNA PREGUNTA ABIERTA DEL PRODUCTO. La pregunta abierta 2 de ADR-002 la señala como una decisión fundacional que no se había tomado cuando se construyó esto, por lo que la herramienta está parametrizada en lugar de ser dogmática: cuando llegue la respuesta, se convertirá en un valor predeterminado, o en una variante menos — un cambio pequeño en lugar de una reescritura. Cada respuesta reafirma el orden que usó, en qué filtró, cuántos candidatos consideró y de dónde provinieron los candidatos.


Configuración

Node 20+ (desarrollado en 22). No hay paso de compilación que configurar; npm test compila primero.

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

Luego, una vez que tenga una clave:

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

Configuración del cliente MCP (stdio):

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

El servidor se niega a iniciar sin una clave, nombrando ambas ubicaciones que buscó, la variable exacta y el hecho de que la clave no es de autoservicio. Un servidor MCP stdio que se inicia limpiamente y luego falla en las siete herramientas es algo miserable de depurar.

Acerca de la clave

La clave de API se envía como un encabezado de solicitud x-api-key. No es un token Authorization: Bearer — ese es el esquema del repositorio hermano de Nitrado, y este repositorio deliberadamente no admite ambos, porque admitir ambos significaría que este código podría transmitir la credencial en una forma que CurseForge nunca documentó.

La clave es otorgada por aplicación a Overwolf y es intransferible. La consecuencia práctica, y la única razón por la que existe este párrafo: una filtración significa revocar y volver a solicitar, y la nueva solicitud es una cola, no un restablecimiento de autoservicio. No puede regenerarla mientras toma un café y no puede tomar prestada la de otra persona. Trátela en consecuencia — .env está en gitignore, .env.example lleva el nombre de la variable y un valor vacío, y ningún valor de clave aparece en ningún archivo confirmado.

No hay una matriz de alcance en este repositorio, y eso no es un descuido: CurseForge no publica ningún ámbito de solo lectura ni ninguna selección de ámbito, por lo que no hay nada que poner en una matriz. La propiedad de solo lectura de este servidor proviene de su propia lista blanca de endpoints, no de una credencial más restrictiva. Tampoco hay un manual de procedimientos para fugas de tokens — una clave filtrada otorga acceso de lectura a un catálogo público más consumo de cuota, lo cual es real y no es la misma categoría que el token de Nitrado del repositorio hermano (documentado como equivalente al control total de un servidor de juego). Ese dimensionamiento correcto se argumenta en ADR-002 §12, y se basa en una afirmación establecida allí para que pueda ser refutada: los datos del catálogo de CurseForge son públicos por construcción.

Redacción, todo ello

Una regla: nunca repetir la clave API. Una función, src/scrub.ts, aplicada a mensajes de error y a cualquier fragmento del cuerpo upstream. Los encabezados de solicitud nunca aparecen en los errores — ni la clave, ni una clave redactada, ni una lista de nombres de encabezados. get_api_diagnostics informa si una clave está configurada y nunca su valor, un prefijo de la misma, ni su longitud.


Comportamientos que vale la pena conocer antes de leer la salida

  • Vacío no es desconocido. data: [] significa que CurseForge respondió "ninguno" — una respuesta real, con la consulta repetida para que puedas ver qué devolvió nada. Un campo ausente es null, nunca 0, "" o []. Una solicitud que no se completó, o una respuesta cuya forma es incorrecta, es un error — nunca un valor.

  • Una clave data faltante es un error, no un resultado vacío. Forzarla a [] convertiría una integración rota en "no se encontraron resultados".

  • Una pagination faltante en un endpoint paginado también es un error. Asumir una página es como una herramienta reporta 50 de 900 mods como si fueran todos (U8 es exactamente esta pregunta abierta).

  • pageSize > 50 es rechazado, no limitado, y también lo es index + pageSize > 10000 — con el tamaño de página legal más grande en ese índice nombrado en el mensaje. Un modelo que pide 200 y recibe silenciosamente 50 razonará sobre una página como si fuera un conjunto.

  • Cuando totalCount supera 10000, la salida de la herramienta dice que la cola es UNREACHABLE, en esas palabras, y aconseja estrechar el filtro en lugar de paginar.

  • El gameId de ASA se descubre en tiempo de ejecución desde GET /v1/games y se almacena en caché durante la vida del proceso; nunca está codificado ni se adivina. Si no se puede resolver, el servidor falla ruidosamente, nombrando lo que buscó y cuántos juegos pudo ver la clave — porque gameId es un filtro de búsqueda requerido, por lo que uno incorrecto devuelve resultados limpios, vacíos y completamente incorrectos en lugar de un error. Establece CURSEFORGE_GAME_SLUG si los candidatos incorporados resultan ser incorrectos.

  • resolve_mod_dependencies está acotado a profundidad 4 y 400 nodos, con un conjunto de visitados para ciclos. Cuando se alcanza un límite, el resultado se reporta como truncado, en esa palabra, con la frontera inexplorada listada.


Estructura del repositorio

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 es generado e ignorado por git, sellado con el commit y una bandera dirty antes de cada ejecución de tsc, y expuesto por get_api_diagnostics. dist/ está ignorado por git y el servidor se ejecuta desde allí como un proceso de larga duración, por lo que "¿qué código produjo esa respuesta?" no se puede responder desde git en tiempo de ejecución — tiene que viajar con el artefacto.

Desviaciones del repositorio hermano, declaradas deliberadamente

Las preguntas abiertas 7 y 8 de ADR-002 piden que estas se nombren donde ocurren:

  • Misma base, deliberadamente. Node ≥20, TypeScript 5.9.3, @modelcontextprotocol/sdk 1.30.0, zod 4.4.3, node:test a través del mismo enumerador scripts/run-tests.mjs. Mismo revisor, mismos modismos, menor costo de leer ambos.

  • @cfworker/json-schema no es una dependencia aquí. Respaldaba la validación de expresiones cron del hermano, y no hay una ruta de escritura que validar.

  • registry.ts se porta en estructura y mantiene tier, pero elimina la maquinaria de modo/lista habilitada — no tendría nada que filtrar, ya que cada herramienta es nivel 1 y cada endpoint es una lectura. Una variable de modo sin nada detrás anuncia un control que no existe. Una aserción de arranque de cinco líneas reemplaza el subsistema.

  • redact.ts no se porta (§12.1). Ver "Redacción, todo ello" arriba.

  • Sin código de error UNKNOWN_OUTCOME. El hermano lo necesita porque una respuesta perdida a un PUT aún puede haber cambiado el mundo. Cada solicitud que este cliente puede hacer es una lectura, por lo que un tiempo de espera realmente significa "no ocurrió" y un reintento es seguro.

  • npm run smoke sale con 0 cuando se niega por falta de una clave. La negativa es el resultado esperado de ejecutarlo hoy, y el banner dice SMOKE NOT RUN de manera inconfundible. Si quieres que un pipeline falle por una clave faltante, condiciona el pipeline a la clave en lugar de a este código de salida.


Registros relacionados

En el repositorio hermano nitrado-ark-mcp, solo lectura desde aquí — nada en ese repositorio fue modificado por este:

  • docs/decisions/EXECUTIVE-BOARD-2026-08-16-curseforge-mods.md — las actas de la junta (DEC-002) que este repositorio ejecuta. Sus Fallos del Presidente son vinculantes.

  • docs/decisions/decision-log.md — DEC-002, y DEC-001 para la división de alcance en la que se basa §10.

  • docs/adr/ADR-001-write-path-enforcement.md — la forma que ADR-002 porta, y la fuente de la regla de normalización, el razonamiento de verificación de arranque y el razonamiento de negarse a iniciar.

Los dos servidores permanecen independientes. nitrado-ark-mcp responde "estos ids de proyecto están en active-mods"; este repositorio responde "el archivo más nuevo del proyecto X es v2.1". El modelo tiene ambos. Ningún servidor llama al otro, y ninguno tiene nunca la credencial del otro.

-
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