CurseForge ARK MCP Server
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-mcpconstruyó sus fixtures de la misma manera cuidadosa a partir de la documentación, y el commit5481c04allí 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.0y 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 sí 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 |
| "¿Qué mods de ASA coinciden con este término?" |
| "¿Qué es el proyecto 777001?" |
| "¿Qué archivos ha publicado este mod?" |
| "¿Qué es este archivo específico?" |
| "¿Hay un archivo más reciente para este mod que el que estoy ejecutando?" |
| "¿Qué requiere este mod?" (por lotes, una solicitud por nivel de árbol) |
| "¿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-urles 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 allowedLa 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 |
|
| resolución de ID de juego, |
E2 |
|
|
|
E3 |
|
|
|
E4 |
|
|
|
E5 |
|
|
|
E6 |
|
|
|
E7 |
|
|
|
Mecánicamente:
Coincidido en
{method, path}conjuntamente. E3 no autorizaDELETE /v1/mods/123. E6 no autorizaPOST /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_dependenciespuede 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 | 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 | Esquema publicado | Cada salida de herramienta |
U4 | Campos de | Esquema publicado |
|
U5 |
| Esquema publicado | Recorrido de |
U6 | El mapeo de enumeración numérica de | NO RESUELTO. Tres intentos contra la documentación; la página muestra | Determina si un borde es requerido, opcional, una herramienta o incompatible, es decir, si se sigue o no. |
U7 | La enumeración numérica de | No resuelto desde la página de documentación. Corroboración parcial solamente: la API de carga utiliza los nombres | Filtrado de |
U8 | Si | Forma documentada; nunca observada | Este cliente genera un error en lugar de asumir una página |
U9 | Si los mods de ASA realmente completan | 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 | 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 |
|
U12 | Comportamiento real de paginación más allá del | Solo restricción documentada | La divulgación de truncamiento en §4.3 |
U13 | URL base | 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:
| También requiere | Significa |
| — | El más reciente de todos los archivos candidatos, por |
|
| El archivo más reciente que declara esa versión del juego |
|
| 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 probeLuego, una vez que tenga una clave:
cp .env.example .env
# set CURSEFORGE_API_KEY, then:
npm run smokeConfiguració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 esnull, nunca0,""o[]. Una solicitud que no se completó, o una respuesta cuya forma es incorrecta, es un error — nunca un valor.Una clave
datafaltante es un error, no un resultado vacío. Forzarla a[]convertiría una integración rota en "no se encontraron resultados".Una
paginationfaltante 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 > 50es rechazado, no limitado, y también lo esindex + 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
totalCountsupera 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
gameIdde ASA se descubre en tiempo de ejecución desdeGET /v1/gamesy 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 — porquegameIdes un filtro de búsqueda requerido, por lo que uno incorrecto devuelve resultados limpios, vacíos y completamente incorrectos en lugar de un error. EstableceCURSEFORGE_GAME_SLUGsi los candidatos incorporados resultan ser incorrectos.resolve_mod_dependenciesestá 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 enumeratorsrc/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/sdk1.30.0,zod4.4.3,node:testa través del mismo enumeradorscripts/run-tests.mjs. Mismo revisor, mismos modismos, menor costo de leer ambos.@cfworker/json-schemano es una dependencia aquí. Respaldaba la validación de expresiones cron del hermano, y no hay una ruta de escritura que validar.registry.tsse porta en estructura y mantienetier, 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.tsno 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 unPUTaú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 smokesale con 0 cuando se niega por falta de una clave. La negativa es el resultado esperado de ejecutarlo hoy, y el banner diceSMOKE NOT RUNde 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.
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