dayz-agentic-modding-mcp
dayz-agentic-modding-mcp
Un servidor MCP que permite a un agente construir un mod de DayZ, comprobar que el cliente compila, ejecutar un servidor de prueba y obtener un veredicto estructurado en lugar de un registro.
El servidor hace el trabajo por sí mismo: llama a FileBank, a la herramienta de firma y al ejecutable de diagnóstico directamente. Un proyecto no necesita su propio script de compilación.
Por qué un perfil
El servidor no sabe nada sobre ningún mod en particular. Todo lo específico vive en un perfil de dos partes:
dayz-mcp.toml— portátil, comprometido con el repositorio del mod: qué mods empaquetar, cómo se ve un arranque saludable.dayz-mcp.local.toml— específico de la máquina, nunca comprometido: dónde viven el juego, las herramientas y el banco de pruebas.
Mezclar las mitades se rechaza al cargar: así es como un repositorio deja de compilarse en
la máquina de cualquier otro. Comienza desde dayz-mcp.example.toml.
Un mod se declara una vez, por nombre: fuentes en <root>/Name por defecto, salida
@Name/addons/Name.pbo. build.sources puede redirigir la fuente de un mod a otro lugar
(por ejemplo, "." para un mod cuyo config.cpp se encuentra en la raíz del repositorio).
La mitad portátil — dayz-mcp.toml
Clave | Significado |
| cómo llamar a este proyecto |
| una entrada por mod, por nombre; la fuente, el pbo y la |
| dónde vive realmente la fuente de un mod, relativo al perfil ( |
| qué nunca debe empaquetarse. Listarlo reemplaza el valor predeterminado por completo; el predeterminado es |
| empaquetar una copia filtrada en lugar de rechazar cuando algo excluido está presente — el diseño que necesita un mod de diseño raíz |
| el directorio contra el que se resuelven todas las rutas de modelo, relativo a este archivo. Debe contener la carpeta de prefijo del mod. Requerido por las herramientas de modelo y por nada más — ver "El pipeline de assets" |
| un script de PowerShell que se ejecuta antes de empaquetar, para proyectos que generan código primero |
| la línea que imprime el mod cuando ha terminado de cargar — ver más abajo |
| pares |
| presupuesto de advertencias; omitir la clave para deshabilitar la comprobación |
| subcadenas que hacen que una ejecución sea mala independientemente de cualquier otra cosa |
| expresiones regulares que marcan errores de script que te pertenecen, usadas por la comprobación de compilación del cliente |
| ruido extra del motor a ignorar, además de la lista incorporada |
La mitad de la máquina — dayz-mcp.local.toml
Clave | Significado |
| la instalación del juego; descubierta automáticamente si está ausente |
| DayZ Tools; descubiertas automáticamente si están ausentes |
| el ejecutable de Blender, solo para |
| el banco de pruebas preparado. El servidor arranca contra él y sus registros se leen desde |
| el nombre del archivo de configuración del servidor dentro del banco (por defecto |
| el puerto que |
| nombres de carpetas de mods, resueltos bajo la carpeta |
| rutas completas a cualquier otra cosa a cargar, para mods que no viven en |
| nombres de carpetas para enrutar a |
La línea de listo
server_start termina cuando expect.ready_line aparece en un registro escrito después
de que ese arranque comenzara. Es la única cosa que el servidor no puede deducir por sí mismo, y
sin ella dos cosas cambian: no se puede esperar nada, por lo que el trabajo de arranque inicia
el servidor, confirma que sigue vivo un momento después y termina diciéndolo;
y log_verdict no tiene línea de la que leer contadores, por lo que expect.counters nunca
coincide con nada. Los errores, los bloqueos y el presupuesto de advertencias aún se juzgan. Un
perfil sin una línea de listo está soportado, no roto — project_open lo dice
en sus notas.
Related MCP server: DayZ API MCP Server
Herramientas
Herramienta | Qué hace |
| leer el perfil, descubrir el juego y las herramientas, informar de lo que falta |
| proyecto actual, servidor en ejecución, trabajos recientes |
| empaquetar y firmar cada mod declarado; devuelve un id de trabajo. Rechaza una segunda compilación del mismo proyecto mientras una siga en ejecución |
| iniciar el servidor de prueba, terminar cuando esté listo. Rechaza si la misión que nombra la configuración no está en |
| pid, si el proceso está vivo, si el registro está creciendo (muestreo con |
| detener el servidor que esta sesión inició (pid opcional para servidores huérfanos) |
| leer — o cambiar deliberadamente — el |
| ejecutar el cliente de diagnóstico y leer sus registros |
| aprobado/fallido con razones: contadores, cadenas prohibidas, presupuesto de advertencias. |
| últimas líneas, opcionalmente filtradas; las mismas dos fuentes |
| estado de un trabajo de larga duración |
| esperar a que un trabajo termine |
| recuperar las salidas de un trabajo completado |
| empaquetar el mod puente, cuyas fuentes vienen con este servidor ( |
| ¿el puente dentro del juego en ejecución sigue funcionando? Informa el número de tick y si avanzó durante |
| descartar el comando atascado en el buzón, nombrando lo que desechó. Rechaza mientras el puente parezca vivo a menos que |
| esperar a que el puente dentro del juego esté realmente reclamando comandos. Llámalo una vez después de que el trabajo de arranque termine, antes del primer comando mundial — ver "el servidor listo no es el puente listo" más abajo |
| instantánea del mundo desde la publicación una vez por segundo del puente: jugadores, posición, salud, manos. Gratis sin argumentos; con |
| crear un objeto en el suelo (sin vida, para que no pueda desaparecer a mitad de verificación), en las manos del jugador o en su inventario |
| mover al jugador a |
| establecer |
| eliminar objetos de una clase cercana. Requiere la clase; nunca elimina a un jugador real |
| qué objetos están cerca, no cuántos: clase, posición, distancia y salud para cada uno. Una página, y lo dice — el total real viene junto con la lista |
| mover el reloj del mundo. Cada campo dejado en -1 mantiene su valor actual, leído primero del motor, porque el motor establece una fecha como cinco números a la vez |
| mover nublado, lluvia, niebla, nevada o viento. Un empujón, no un bloqueo: el motor sigue simulando el clima después, y tanto la herramienta como el mod lo dicen |
| ejecutar la acción propia de un mod a través de la puerta del motor — ver más abajo |
| la escotilla de escape: un verbo arbitrario a través del mismo transporte, marcado como no estándar en cada respuesta |
| iniciar el cliente del juego y conectarlo al stand; devuelve un id de trabajo. Siempre en ventana. Termina cuando el puente informa |
| client_status() | pid, geometría de la ventana, si la ventana está minimizada o en primer plano, el ajuste de fondo, el número de jugadores y si hay un controlador virtual conectado |
| client_stop() | detener el cliente que esta sesión inició y desenchufar el controlador virtual |
| client_shot(path) | captura la ventana del cliente en un PNG, con lit_fraction — el número que distingue un fotograma real de uno completamente negro. No necesita foco |
| client_move(x, y, seconds) | mueve al personaje con la palanca izquierda. Analógico, y la única vía que mueve al personaje en absoluto. No necesita foco |
| client_look(x, y, seconds) | gira la cámara con la palanca derecha. No necesita foco |
| client_press(button, seconds) | un botón de mando, de una tabla cerrada de catorce nombres. No necesita foco |
| client_chat(text, color) | escribe una línea en el chat — entregada por el puente en el lado del servidor, así que no necesita teclado, ni ventana, ni foco |
| client_type(text, submit) | escribe en un campo de entrada del lado del cliente con pulsaciones reales de teclado. La única herramienta aquí que pasa a primer plano, y lo dice en su respuesta |
| client_verdict(since) | juzga al cliente en vivo por su propio .RPT — un veredicto de errores y cuelgues; ver más abajo |
| ui_menu() | lo que está haciendo la interfaz del cliente: clase de menú abierta, cursor, diálogo. Gratis — republicado en cada tick |
| ui_tree(root, depth, limit) | el árbol de widgets del cliente: ruta, clase, nombre, visibilidad, rectángulo de pantalla, profundidad y texto. Una página, y lo dice |
| ui_find(name, class_name, text, root) | el mismo recorrido, filtrado en el cliente para que el árbol completo nunca tenga que viajar |
| ui_click(path, expect_name, expect_class, via) | pulsa un widget. via="script" pasa por el manejador del menú abierto sin foco; via="cursor" coloca el ratón real sobre su rectángulo |
| ui_text(path, text, expect_name) | escribe en una caja de edición y lee el valor de vuelta del widget |
| mod_lint(mod, strict) | juzga el Enforce Script sin empaquetar ni arrancar nada. mod_build lo ejecuta primero y rechaza lo que este rechaza |
| knowledge_build(layer, full, only) | construye o refresca una capa del índice de la API; devuelve un id de trabajo. only=[path] relee exactamente los archivos que nombres |
| knowledge_status() | qué contiene cada capa, cuán antigua es y si sigue coincidiendo con lo que hay en disco |
| knowledge_find(name, kind, owner, layer, prefix, limit) | encuentra una clase, método, constante, enum o clase de config por nombre |
| knowledge_show(name, ..., body) | una declaración completa: firma, miembros, cadena de herencia y la fuente misma — leída directamente de un archivo si es allí donde vive |
| knowledge_overrides(name, owner, layer) | quién sobrescribe esta clase o método |
| knowledge_callers(name, kind, owner, layer) | quién llama a este método o construye esta clase — cada punto de llamada, con la clase y el método que lo hizo |
| asset_export(blend, mod, source, name) | exporta un modelo desde un .blend a build.project_root, sin interfaz; devuelve un id de trabajo. El primer paso opcional — ver más abajo |
| asset_build(mod, source, deploy) | binariza los modelos de un mod desde sus fuentes MLOD, juzga lo que salió y solo entonces lo pone en el mod; devuelve un id de trabajo |
| asset_check(mod, model) | juzga los modelos y texturas que un mod ya incluye. No construye nada, no necesita DayZ Tools, responde en milisegundos |
| asset_convert(source, output) | convierte una textura entre .png y .paa, y juzga el resultado |
Firmas, y por qué el mensaje del propio motor te despista. Con
verifySignatures = 2 un servidor rechaza a todo cliente con el código 118 y "falta
dta.bin.pbo" — un nombre de archivo original, sin mención alguna a las firmas. La
causa suele ser el llavero de claves: esta herramienta lanza el ejecutable de diagnóstico desde la
instalación del CLIENTE, así que el motor lee keys junto a ese ejecutable, mientras que
dayz.bikey — la clave que firma los pbo del propio juego — se distribuye con la instalación
separada de DayZServer. Una carpeta keys que falta, o que solo contiene la clave de un mod,
deja al servidor sin poder verificar nada, incluido el contenido original. client_start
se niega y nombra cuál de los tres es; server_start solo avisa, porque un
arranque sin cabeza y sin cliente sigue siendo útil. Un pbo sin firmar en la línea
-mod del cliente se nombra de la misma manera — incluido el puente de este propio servidor, que
está empaquetado sin firmar a propósito.
Tres límites que el motor impone a las herramientas de UI, y ninguno se ha sorteado:
un TextWidget simple tiene SetText y no GetText en ningún lugar de enwidgets.c, así que
la cadena de una etiqueta no se puede leer en absoluto — lo que SIGNIFICA la interfaz de un mod sigue siendo una cuestión para
el puente del lado del servidor, donde los datos son reales. Un clic a nivel de script solo alcanza el
menú scripteado abierto, porque Widget tiene SetHandler y no GetHandler; via="cursor" está
ahí para todo lo demás. Y el cliente tiene que cargar el puente: un pbo lleva ambas
mitades, así que un perfil que lo liste bajo mods.server_only lo mantiene fuera de la línea -mod
del cliente — ese caso se rechaza por nombre en lugar de responderse con un árbol vacío.
job_wait es la herramienta pensada para esperar, y su timeout está limitado a 600
segundos por muy grande que sea el valor que se pase. Otras dos herramientas duermen: server_status
muestrea el log dos veces, separadas por pulse_seconds, con un límite de 10 segundos — esa pausa
es como distingue un arranque lento de uno colgado — y bridge_status muestrea el
tick del puente dos veces, separadas por window, con el mismo límite de 10 segundos, por la misma
razón. Todo lo demás devuelve inmediatamente; el trabajo que lleva minutos ocurre
detrás de un id de trabajo.
El mod del puente
bridge_build empaqueta bridge/ desde el propio repositorio de este servidor en
@DZMCP_Bridge a su lado. Es el mod del servidor, no el tuyo: una copia sirve a
cada proyecto, nada se escribe en tu repositorio excepto el registro del trabajo,
y no se usa la clave de firma de ningún proyecto — un pbo de -serverMod nunca se entrega a un
cliente para que lo verifique, así que se construye sin firmar y su carpeta de salida se mantiene libre
de firmas y claves.
Construirlo no lo carga. Eso sigue siendo decisión de tu perfil, porque el
puente es un pbo extra en el servidor y una ejecución sin él tiene que seguir siendo posible.
Para adjuntarlo, añade dos líneas a dayz-mcp.local.toml (las mismas dos
que imprime el resumen del trabajo de bridge_build):
[mods]
extra = ["<path printed by bridge_build>/@DZMCP_Bridge"]
server_only = ["@DZMCP_Bridge"]server_only es lo que lo enruta a -serverMod en lugar de -mod. Sin ello,
el servidor arranca perfectamente y bridge_status informa de que el puente nunca
escribió ningún estado — lo cual es cierto, y fácil de confundir con un puente roto.
bridge_status también informa del buzón de comandos. Dentro del juego solo el mod
lo vacía, reclamando el comando; en este lado lo hacen bridge_clear y
el borrado previo al arranque de server_start. Así que un comando enviado mientras el servidor estaba
caído, o antes de que el puente se adjuntara, no se descarta ni caduca por sí
solo — sigue bloqueando
cada envío posterior, y un servidor arrancado fuera de estas herramientas lo recogería.
server_start borra ambos archivos de transporte antes de cada arranque, así que un servidor
iniciado a través de esta herramienta nunca ejecuta un comando de una sesión anterior; eso es
higiene, no un sustituto de saber que el comando está ahí. El estado vuelve
como stale_command, y
bridge_clear() es la salida. El borrado es una herramienta separada a propósito:
descartar un comando en cola es una decisión, no algo que una comprobación de estado
deba hacer a tus espaldas. Se niega mientras el puente parezca vivo a menos que
pases force=True, y de cualquier manera informa del id de comando que descartó.
Lo que bridge_status puede distinguir
El tick por sí solo no basta para juzgar un puente, porque se reinicia a 0 en cada
arranque mientras el archivo de estado sobrevive en el directorio de perfil. Cada respuesta
lleva el veredicto propio del canal en heartbeat, y los cuatro son hechos genuinamente
distintos:
|
| significado |
|
| el tick avanzó dentro de una sesión — la única respuesta de actividad con |
|
| un mundo nuevo apareció entre las dos muestras: vivo, no congelado, y todo lo enviado a la sesión anterior ha desaparecido |
|
| el mismo mundo visto dos veces, sin moverse — un problema del lado del script, así que |
|
| no se pudo leer una muestra (o |
Cada respuesta que leyó una muestra también lleva session_id — el id del mundo
en vivo — y una respuesta restarted lleva también previous_session_id, para que quien llama pueda
decir qué mundo desapareció.
no_server, stale_command, no_state_file, invalid_state,
unreadable_state y outdated_bridge vienen antes que todo eso: nada está
en ejecución, un comando está atascado, el mod no está cargado, el documento de estado es JSON válido
con un campo nombrado incorrecto (dice cuál, y lo comprueba dos veces antes de decirlo),
el archivo nunca se parsea en absoluto, o se parsea pero es anterior al protocolo de este servidor
(reconstrúyelo con bridge_build).
Los comandos del mundo
Las herramientas del mundo hablan con el puente a través de dos archivos JSON en el directorio
-profiles del servidor: un buzón de comandos (escrito atómicamente desde este lado,
eliminado por el mod como su reclamo) y un archivo de estado que el mod sobrescribe una vez por
segundo. Enforce Script no tiene rename, así que el mod no puede escribir atómicamente —
el lector tolera escrituras rasgadas en su lugar, y una lectura fallida nunca es noticia.
Cuatro hechos, todos medidos en un servidor en vivo, deciden cómo usarlos:
Que el servidor esté listo no significa que el puente esté listo. El puente empieza a reclamar comandos
decenas de segundos después de que el servidor informe de que está listo — la dispersión observada hasta ahora
es de 18–38 segundos, y varía de un arranque a otro.
Un comando enviado en esa ventana no se rechaza — se reclama tarde y
se completa después de que quien llama haya abandonado. Así que: server_start, espera al trabajo de arranque,
luego world_ready(), y después los comandos. Toda herramienta del mundo también se niega de antemano
si el tick no se mueve, nombrando a world_ready como el remedio.
Todo valor de argumento cruza el cable como cadena. El parser del mod es
estricto: un número JSON en cualquier lugar de args rechaza todo el bloque de args. Las
herramientas convierten números y booleanos a cadena por sí mismas y rechazan valores sin
forma de cadena fiel (listas, diccionarios, None). Las posiciones viajan como una sola cadena,
"x y z".
Una negativa es un resultado. La frase propia del mod vuelve verbatim como error: "no player is on the server", "the class does not exist", "the action's own Can() said no". Que no haya nadie conectado es el estado normal de un servidor sin cabeza, y todo verbo que necesita un jugador lo dice en lugar de no hacer nada silenciosamente.
El id de sesión protege contra el comando de ayer. Cada comando lleva la sesión que el puente publicó más recientemente; el mod rechaza cualquier comando dirigido a otra sesión (o a ninguna) sin ejecutarlo. Un comando escrito mientras el servidor estaba caído nunca puede dispararse contra un mundo recién arrancado. Las herramientas sellan la sesión automáticamente — solo importa si escribes el buzón a mano.
Acciones, y por qué no hay un diccionario de verbos
Un verbo semántico como "entregar la muestra" miente: en un mod real las mismas palabras
significan cosas distintas según qué dispositivo esté cerca, la facción del jugador,
y qué ya está desbloqueado. Ese contexto no es enumerable, así que el puente
no lo intenta. world_action toma el nombre de clase de una acción, un objetivo y el
objeto sostenido, y pide al motor que la ejecute a través de su propia puerta — la misma por la que
pasa una pulsación de tecla. La aplicabilidad la decide el propio Can() de la acción,
y su rechazo es un resultado de prueba significativo, no un fallo de la herramienta. Las
respuestas distinguibles: gestor ocupado, jugador ya actuando, jugador
corriendo, clase de acción desconocida, y "el propio Can() de la acción dijo que no".
"Aceptada" tampoco es éxito — el comando sigue en ejecución hasta que el motor
libera realmente la acción, y cada ruta de fallo libera el gestor para que
el jugador pueda seguir actuando después.
world_exec es la vía de escape
Cualquier cosa que un mod exponga que no sea una acción — "cuántos puntos hay en el
fondo de la facción" — pasa por world_exec(verb, args): un verbo arbitrario sobre
el mismo transporte. Cada respuesta está marcada como non_standard: este servidor
no conoce el verbo, no lo valida, y no responde por lo que el mod hace con él.
Un verbo que la compilación del puente no conoce devuelve la lista de los verbos que sí conoce.
Un proyecto que necesita su propio verbo edita su propia copia del
despachador del puente (el comentario sobre KnownVerbs() en
bridge/scripts/5_Mission dice exactamente dónde); no hay maquinaria de
registro a propósito — un verbo que este servidor escribiera y validara sería un verbo
por el que este servidor responde.
El cliente: tres capas de entrada, y por qué son tres
El puente llega al servidor. Lo que no puede hacer es mirar la pantalla del
cliente o actuar a través del cliente — caminar un personaje por el terreno, abrir un
menú, rellenar un campo que un mod dibujó. Las herramientas client_* son eso, y usan
tres vías diferentes porque ninguna de ellas puede hacer el trabajo de las otras dos.
Cada línea de abajo es una medición contra un cliente en vivo, no una intención de diseño.
Vía | Qué hace | Necesita el primer plano |
el puente ( | el mundo, y texto en el chat | no |
un gamepad virtual, ViGEmBus ( | movimiento, cámara, y algo de interfaz | no |
pulsaciones reales de teclado, | texto en un campo que solo existe en el cliente | sí, y lo toma |
La emulación de teclado no mueve al personaje, y los mensajes de ventana no hacen
nada en absoluto. Scancodes de SendInput con el primer plano verificado: 25 s de
avance, 0 m. PostMessage/SendMessage WM_KEYDOWN en la ventana principal
y sus hijos: 0 m, y tampoco reacción de los menús. El motor
lee el movimiento de la entrada sin procesar e ignora las teclas emuladas, que es por lo que ninguna
herramienta aquí ofrece un mensaje de ventana.
El gamepad virtual sí lo mueve, sin foco, y es ANALÓGICO — la razón por la que se queda incluso donde una tecla bastaría. Medido en una ejecución con una aplicación de terceros manteniendo el primer plano durante todo el tiempo:
stick fully forward, 10.0 s -> 38.40 m (3.84 m/s)
stick at 0.3 forward, 8.0 s -> 11.34 m (1.42 m/s)Misma vía, mismo personaje, 2,7× la velocidad solo por la deflexión de la palanca. "El personaje camina, no corre" no se puede expresar con una tecla, que solo conoce encendido y apagado. En la misma ejecución el personaje caminó unos 141 m por su cuenta, y el propio recuento del mod de objetos a menos de 10 m de él pasó de 1 → 0 → 1 al salir del sitio y volver — un cambio de estado causado por presencia, que un teletransporte no puede producir.
Parte de la interfaz responde al pad, y parte no. Medido, con
la ventana del juego detrás de otra aplicación todo el tiempo: back abre y
cierra el inventario, start abre el menú de pausa, b lo cierra — todas con
la pulsación por defecto de 0,1 s, así que una pulsación es suficiente para que el motor la registre. Pero a
no movió nada, ni a 0,1 s ni a 0,5 s, y tampoco la cruceta dentro de esas
pantallas: el cliente no cambió al modo de navegación con mando, así que no
había ningún elemento enfocado sobre el que un confirmar pudiera actuar. Trata el cierre de menús como
una tarea del gamepad y la confirmación de menús como no demostrada.
Los ojos tampoco necesitan foco. Una captura es un fotograma en vivo con la ventana
en la parte más baja del orden z (lit_fraction 0,9997 sin foco, 0,9997
con foco en la misma sesión). El único estado que las derrota es una ventana
minimizada, cuya área de cliente se colapsa a 0×0 — rechazada con una
razón en lugar de guardarse como una imagen vacía de apariencia válida.
Todo ese comportamiento en segundo plano descansa en un único ajuste del cliente, pauseMode
(GAME → UPDATE IN BACKGROUND). En el valor medido aquí, el cliente sigue
dibujando y simulando sin foco, que es por lo que el fotograma está vivo y la
palanca sigue moviendo al personaje. Con "sin gráficos" ambos se detendrían silenciosamente —
un fotograma congelado se ve exactamente igual que uno en vivo. Así que client_start y
client_status LEEN ese ajuste y avisan; nunca lo escriben, porque pertenece
a quien sea dueño de la máquina.
client_type es la única herramienta que toma la pantalla, y es honesta
al respecto: la respuesta lleva foreground_taken y una frase que dice que la
persona en la máquina no pudo escribir en su propia ventana mientras se ejecutaba. Verifica
el primer plano con GetForegroundWindow después de pedirlo, porque
SetForegroundWindow devuelve éxito sin haber hecho nada cuando Windows se niega
— y escribir a ciegas envía las pulsaciones a la ventana que la persona está
usando realmente. Cuando no se puede obtener el primer plano, no se escribe nada y el
rechazo nombra el proceso que lo tiene.
ViGEm es emulación de un dispositivo real y esto es un banco de pruebas. El controlador está firmado y se instala sin reiniciar, y el gamepad es un dispositivo nuevo en lugar de un filtro sobre el propio teclado y ratón de la máquina — un controlador de filtro se probó aquí una vez y le costó al dueño de la máquina toda la entrada de teclado y ratón hasta que se deshizo a mano. Nada de eso es una promesa sobre el anticheat en un servidor en vivo, y nada en esta fase hace una.
El índice de conocimiento
Un agente que escribe un mod no deja de hacerle al juego las mismas preguntas: ¿existe tal
API, cómo se llama, dónde está declarada, quién la sobrescribe. Responderlas
significaba desempaquetar scripts.pbo y barrer el texto — y cada sesión
pagaba de nuevo. knowledge_* convierte ese trabajo en una pregunta.
Es un archivo SQLite plano en el propio .dayz-mcp/ del proyecto, construido por
este servidor a partir del juego, los mods que un proyecto declara y las fuentes
propias del proyecto. Sin embeddings, sin servicio externo, sin clave.
Tres capas, y por qué sus ritmos difieren
Capa | Fuente | Se vuelve obsoleta cuando |
| el juego: | el juego se actualiza |
| los archivos de los mods que declara el perfil, leídos sin desempaquetarlos | una dependencia se actualiza, o el conjunto declarado cambia |
| las fuentes propias del mod, leídas donde están | cada edición |
Un índice construido de una sola vez estaría mal al minuto de estar bien: el
juego se mueve unas pocas veces al año, una dependencia unas pocas veces al mes, y el
proyecto entre un turno de agente y el siguiente. Así que cada capa se construye, envejece y
mide por su cuenta, y cada construcción es incremental — las fuentes sin cambios se
omiten por tamaño y hora de modificación, y only=[path] omite incluso el recorrido
que las descubre.
Una respuesta lleva la edad de la capa de la que proviene
La obsolescencia se mide, no se adivina: una capa registra el tamaño y la hora de modificación de cada fuente que leyó, y eso se compara contra los archivos tal como están ahora.
Cada respuesta nombra las capas que usó y la edad de cada una. Una respuesta con ningún resultado nombra cada capa que buscó — "no encontrado" vale exactamente tanto como las capas que hay detrás están al día.
La frescura de la capa del proyecto se mide en cada búsqueda, contribuya o no. Ese es el caso peligroso: un agente añade una clase, pregunta por ella, y una capa construida hace un minuto dice "no encontrado" — una afirmación segura sobre código que existe.
Una búsqueda sobre una capa que nunca se construyó es rechazada, y el rechazo nombra la llamada que la construye. "No encontrado" y "no buscado" son hechos diferentes, y solo uno de ellos es seguro para actuar sobre él.
La restricción lleva la misma trampa un nivel más abajo, así que una respuesta restringida vacía informa de dónde sí existe el nombre: preguntar
kind='class'por un nombre que el juego declara solo en un config obtiene un "no" verdadero que se lee como "el juego no tiene tal clase".
Las clases de config viven bajo kind='config', no kind='class'. Contadas en el
propio índice de esta máquina del juego: 88 102 clases de config contra 43 595
declaraciones de script de todo tipo juntas, así que mezcladas en un solo tipo entierran cada
respuesta de script. Separadas, "¿tiene el juego una clase de objeto llamada X?" es
una pregunta que puedes hacer con exactitud.
Lo que no responde
El índice responde qué existe: clase, método, firma, dónde está declarado, quién
sobrescribe. No responde qué es correcto — que modded class X extends X compila y falla silenciosamente al aplicarse, que _co cuesta el canal alfa,
que binarize toma directorios en lugar de archivos. Nada de eso es derivable
de las fuentes; se aprendió por las malas y vive en la habilidad de modding y
en el propio mod. El índice no intenta reemplazar a ninguna de las dos, y no intenta
entender qué significa un campo o por qué una clase está ahí.
La búsqueda semántica está deliberadamente ausente
La decisión se tomó por medición, no por precaución: cada búsqueda que dio forma a las fases anteriores de este servidor fue una búsqueda por nombre. Y un índice de embeddings rompería la regla que el resto de este servidor mantiene — instálalo y funciona, sin servicio externo y sin clave. El proyecto predecesor sobre el que este deliberadamente no se construyó documenta su capa de conocimiento como local y gratuita mientras su código importa un cliente de embeddings de pago, falla sin clave y lleva precios hardcodeados. Sus dos herramientas de búsqueda también se cuelgan para siempre, porque el cliente que hay detrás se creó sin timeout; de ahí el límite bajo el que corre cada búsqueda aquí. Si la búsqueda exacta resulta no ser suficiente, la búsqueda semántica es una fase separada con una condición: el modelo viaja dentro de la entrega.
Los números medidos
En esta máquina — el juego con 2810 archivos de script, 35 mods instalados, un proyecto real de 41 fuentes — a través de las herramientas, no de sus internals:
Construcción | Resultado | Tiempo |
| 2927 fuentes, 131 697 declaraciones (41 no dieron nada) | 70,2 s |
| 8 archivos, 10 925 declaraciones | 0,9 s |
| 523 archivos, 204 768 declaraciones, 3 archivos ilegibles y nombrados | 139–147 s |
| 41 fuentes, 1196 declaraciones | 0,12 s |
El índice en disco: 74,7 MB para las tres capas de un proyecto real; 110 MB para los 523 archivos de dependencias por sí solos. Esos archivos son 92 GB, y ninguno de ellos está desempaquetado.
Los puntos de llamada son el precio que paga el índice. Los propios scripts del juego contienen 43 579 declaraciones y 113 703 puntos de llamada, y registrar el segundo conjunto aproximadamente duplica el índice: medido solo en la capa del juego, 23.8 MB y 3.7 s para construir sin ellos, 49.6 MB y 4.5 s con ellos. Ese es el precio de poder responder «quién llama a esto», y se declara aquí en lugar de descubrirlo más tarde en un disco lleno.
Respuesta | Tiempo |
| 4.2 ms de extremo a extremo, de los cuales 3.0 ms son el recorrido del proyecto |
| 3.2 ms de consulta |
| 4.2 ms |
| 0.38 ms de consulta |
| 277 ms de comprobaciones de texto, 7 ms de comprobaciones de índice |
Medido en un banco de pruebas en vivo, tres arranques: world_time_set(hour=3, minute=7) movió el reloj a 2026-09-20 03:07 y dejó la fecha donde estaba; world_weather_set("fog", 0.9, seconds=2) llevó la niebla publicada de 0.085 a 0.900 y la mantuvo; world_entities(pos="7500 0 7500", radius=150, limit=5) enumeró 5 de 171 objetos con truncated: true. Las distancias volvían a 320 m para un radio de 150 m hasta que se hicieron horizontales, que es lo que mide la propia prueba de radio del motor.
| knowledge_show, una clase con 400 miembros y su ascendencia | 6.8 ms |
| knowledge_status, las tres capas medidas | 41 ms (110 ms en la primera llamada tras una compilación) |
Incrementalidad, en el proyecto real: una reconstrucción completa 136 ms; un archivo editado encontrado por el recorrido 8.8 ms (15×); el mismo archivo nombrado mediante only= 5.8 ms (23×). En un árbol de 2810 archivos el recorrido domina y only= vale mucho más — pero en un proyecto de este tamaño, 15× es lo que una reconstrucción ordinaria compra realmente.
El tope se hace sentir de verdad: una consulta medida en 77 ms, ejecutada bajo un tope de 19.3 ms, se detuvo a los 19.9 ms, y la conexión siguió respondiendo.
El pipeline de assets
Llevar un modelo de Blender a un mod son diez pasos, y hasta esta fase todos se ejecutaban a mano. El valor no está en lanzar las herramientas. Está en que cada herramienta de esta cadena es estructuralmente incapaz de informar de un fallo, y cada uno de esos silencios ya había costado días.
Medido con los binarios reales, no supuesto:
Lo que ocurrió | Lo que devolvió la herramienta |
a | 0, un directorio de salida vacío, ni una línea de texto |
| 0, un ODOL de 46 190 bytes cuando una compilación correcta es de 58 644 |
a |
|
el exportador de Blender con sus propios argumentos predeterminados |
|
De modo que la regla sobre la que se construye todo este espacio de nombres: el veredicto se lee del artefacto, nunca del informe de la herramienta. El código de salida se registra y no se cree en ninguna de las dos direcciones.
La raíz se declara, no se supone
binarize no tiene ninguna opción de raíz de proyecto — la lista completa de opciones se enumeró contra el binario real. La raíz es el directorio de trabajo del proceso. El mismo comando, la misma entrada, un directorio distinto, y sale un ODOL válido con rutas de textura plausibles que el motor renderiza sin texturas, con un código de éxito y sin queja alguna. El add-on exportador de Blender tiene la misma raíz en una preferencia propia, recordada del último proyecto que estuviera abierto: en la máquina donde esto se desarrolló apuntaba a un directorio de una sesión no relacionada, y contra una raíz equivocada el add-on tampoco falla — elimina la letra de unidad, conserva el resto y escribe rutas que parecen rutas.
build.project_root es ese directorio, declarado una vez en la mitad portable del perfil. El servidor lo establece como directorio de trabajo del binarizador y lo introduce en el add-on durante la ejecución, de modo que lo que el add-on tenga almacenado no decide nada (se informa de ello, para que puedas ir a arreglarlo). Eso es lo que hace que una raíz equivocada sea imposible en lugar de detectable, y es por lo que la clave es obligatoria antes de que se ejecute nada con forma de modelo.
Las negativas que produce ocurren antes de que exista un proceso — medido en 0.0003 s — y una compilación rechazada deja el modelo que el mod ya distribuye intacto byte a byte.
Doce comprobaciones sobre el artefacto, y cuatro de ellas rechazan
asset_check las ejecuta sin construir nada y sin DayZ Tools, porque un clon recién hecho debe poder preguntar si lo que distribuye está sano. Cuatro rechazan: hay un modelo construido y es un ODOL (C1), ninguna referencia escapa del mod (C3), un material fue realmente incrustado (C4), y nada ya binarizado se vuelve a ofrecer a binarize (C10). El resto avisa: referencias colgantes, un rvmat que apunta a otro mod, una transparencia perdida por DXT1 (C7), una animación que nunca llegó al artefacto, un model.cfg que no es con el que se construyó el artefacto, una huella estructural que ya no coincide con lo que desplegó la última compilación. Cada hallazgo dice qué hacer.
C4 es el que merece la pena conocer. Cuando binarize resuelve un rvmat copia las texturas de etapa propias de ese material en el modelo — fresnel, #(argb,8,8,3), env_land_co.paa, _nohq, _smdi — cadenas que ningún MLOD contiene. Seis artefactos de seis se separaron correctamente con esa única prueba, y encontró en esta máquina un modelo roto del que nadie sabía.
El paso de Blender es opcional
asset_export es la única herramienta aquí que necesita Blender, y todo lo posterior funciona con un .p3d de cualquier procedencia — una exportación manual, un archivo de un colaborador, un modelo subido hace años. Una máquina sin Blender compila y distribuye un mod perfectamente; la negativa lo dice en lugar de presentarlo como una instalación rota. Sí necesita que el add-on exportador esté habilitado en el Blender que encuentre, y nunca escribe de vuelta las preferencias de usuario de Blender (verificado: el archivo de preferencias era byte a byte idéntico tras cada ejecución).
Exportar y compilar son dos llamadas en lugar de una, porque cada mitad tiene su propio veredicto y una compilación rechazada por una y permitida por la otra no es una decisión.
La igualdad de bytes nunca se promete
Ninguna de las dos mitades de este pipeline es reproducible, y el diseño lo dice en lugar de fingir:
La exportación. Siete exportaciones de un único archivo fuente sin cambios — tres de una sesión, tres de otra, y una hecha a mano en la GUI meses antes — dieron siete SHA-256 diferentes con un tamaño constante de 334 032 bytes. La diferencia es el orden de un bloque interno.
binarize. Cuatro ejecuciones sobre una misma entrada sin cambios dieron tres resultados diferentes: el tamaño varió en 5 bytes y dos fragmentos de 8 bytes se filtraron fuera de regiones comprimidas.
Por lo tanto, un modelo nunca se almacena en caché ni se compara por hash de contenido. Lo que se compara es una huella estructural — el tipo de archivo, su número de LOD y su conjunto de nombres. En las siete exportaciones, esa huella fue un único valor.
Las cifras medidas
Un modelo pequeño, en esta máquina, a través de las herramientas:
Paso | Resultado | Tiempo |
| MLOD, 334 032 B, 5 LODs, limpio | 2.1 s (unos 8 s en arranque en frío) |
| ODOL v55, 58 646 B, 4 LODs, los cinco marcadores C4 | 43.8 s (75.6–78.7 s medidos en cuatro ejecuciones anteriores) |
| 1 modelo y 10 pares de texturas evaluados | milisegundos |
| un PNG a DXT1, 50 764 B | 0.52 s |
una negativa con una raíz equivocada | antes de que se inicie ningún proceso | 0.0003 s |
Ambos registros son casi en su totalidad texto repetitivo, y lo que se silencia se cuenta en lugar de descartarse: las 169 líneas de Blender se redujeron a 4, y las 91 de binarize a 6 — uno de esos seis siendo la única queja genuina del modelo.
Encadenados de principio a fin, la exportación y la compilación reprodujeron un modelo que se había hecho a mano meses antes: mismo tipo, mismos 4 LOD, las mismas 50 cadenas, y un tamaño con un byte de diferencia.
Lo que nada de esto responde
Si el modelo se ve bien, está escalado bien, está bobinado correctamente, tiene una colisión. Nada fuera del juego responde a eso. C1–C12 acortan el camino hacia ello; no lo sustituyen.
Limitaciones conocidas
La detección de pbo obsoleto se basa en mtime, no en el contenido.
mod_buildrechaza un pbo recién construido que es más antiguo que sus fuentes -- la causa habitual es un servidor en ejecución que aún mantiene el archivo antiguo abierto, por lo que el empaquetado produjo silenciosamente nada. Perogit checkoutcambia el tiempo de modificación de un archivo sin cambiar su contenido, por lo que un pbo perfectamente bueno construido justo después de cambiar de rama también puede activar esta comprobación. Simod_buildinforma de "pbo obsoleto" inmediatamente después de un cambio de rama, esta es la razón probable, no un fallo real de empaquetado -- reconstruye y pasará. Una herramienta madura en este espacio pasó a un hash de contenido exactamente por esta razón; eso es trabajo futuro aquí, no hecho en esta fase (verpacker.py,pack_one).Una carpeta de origen de mod se empaqueta entera.
mod_buildse niega a empaquetar un mod cuyo directorio de origen contenga cualquier cosa que coincida conbuild.exclude(el valor predeterminado de siete patrones se enumera arriba) en lugar de enviarla silenciosamente dentro del pbo publicado. Se niega independientemente debuild.excludecuando el origen contiene los artefactos propios de este servidor -- las claves de firma, cualquiera de las dos mitades del perfil, el almacén de trabajos, la compilación anterior del mod -- porque empaquetarlos publica la clave de firma privada, y ningún proyecto debería tener que configurar eso. Por defecto no prepara primero una copia filtrada: una copia siempre es más nueva que las fuentes, lo que deshabilitaría permanentemente la comprobación de pbo obsoleto anterior si esa comprobación midiera la copia.build.stage = trueopta por copiar de todos modos -- seguro solo porque la comparación de pbo obsoleto siempre mide el árbol de origen original, nunca la copia. Este es el diseño que necesita un mod cuyo origen es la raíz del repositorio (siempre contiene al menos.git).Un veredicto juzga el registro completo, no solo las líneas de tu mod.
log_verdictlee el registro del stand al que está apuntado, por lo que un stand compartido con otros mods cuenta sus advertencias contra tu presupuestoexpect.max_warningsy sus errores como razones. Dos proyectos que comparten unmachine.stand_rootverán la línea base del otro. O bien se da a cada proyecto su propio stand, o se establece el presupuesto sabiendo qué más está cargado. Un filtro con ámbito de proyecto, simétrico aexpect.error_regex, es el refinamiento obvio y no está implementado.expect.noiseno puede rescatar una línea que ya cuenta como error. La clasificación está ordenadaforbid→ crash → error → noise → advertencia, por lo que una línea que contieneERRORoFATAL(o una de tus cadenasforbid) se decide antes de que se consulte noise. Ese orden es deliberado -- si noise coincidiera primero, un substring inocuo podría tragarse una línea fatal -- pero significa quenoisesolo puede suprimir advertencias y líneas ordinarias, nunca degradar una línea de nivel de error.client_verdictes un veredicto de errores y fallos, no de disponibilidad.[expect]describe el registro del servidor: su línea de listo y sus contadores los imprime la inicialización del lado del servidor de un mod, ymax_warningses un presupuesto contado sobre ese mismo registro. Un.RPTde cliente no contiene nada de eso, por lo que esas tres claves no se aplican deliberadamente aquí y la respuesta las enumera ennot_applied.forbid,error_regexynoisetratan sobre el texto de una línea de registro y siguen aplicándose. No hay línea de listo del lado del cliente que declarar; si el cliente entró se responde con el recuento de jugadores que esperaclient_start, no con su registro.Las herramientas de cliente se unen a un stand que esta sesión no inició;
client_chatno puede.client_startse conectará felizmente a lo que ya esté en el puerto, y dice de quién es. Pero el chat se entrega del lado del servidor, a través del mismo canal que las herramientasworld_*, y ese canal solo actúa sobre un servidor que esta sesión inició -- por lo que en un stand prestado todo funciona exceptoclient_chat. La negativa nombra los pids que mantienen el puerto en lugar de sugerir unserver_startque los rechazaría.El chat no es accesible desde el gamepad, y confirmar un menú tampoco. El juego vincula su línea de chat a Enter y a nada más, y no hay teclado en pantalla, por lo que el texto es o un mensaje de puente (
client_chat, gratis) o pulsaciones de teclas reales (client_type, cuesta el primer plano).client_type("", submit=True)envía Enter solo, que es como se abre la línea de chat -- y, según la evidencia anterior, la única confirmación que tiene el conjunto de herramientas.Cada búsqueda de conocimiento paga un recorrido del árbol del proyecto. Ese recorrido es como se mide el desfase de la capa de proyecto en cada respuesta, que es la única propiedad para la que existe el índice. Cuesta 3,0 ms en un mod real de 41 fuentes (cuyo árbol contiene unas 1800 entradas) y 21 ms en un árbol de 2810 archivos. Almacenarlo en caché durante un segundo o dos eliminaría el coste y restauraría exactamente la ventana de silencio que el diseño rechaza; si alguna vez se vuelve demasiado caro, ese intercambio debe hacerse deliberadamente, no por accidente.
Una compilación siempre pasa por un trabajo, y el trabajo cuesta más que una compilación pequeña. Tiempo de respuesta medido en 70–95 ms frente a una reconstrucción de proyecto de 6 ms:
job_waitsondea a 100 ms. La forma única es deliberada -- un llamador no debe tener que saber qué bloques de compilación -- y nada te obliga a esperar, porque la siguiente búsqueda mide la capa misma.La capa de dependencias se mide contra el perfil tal como está ahora. Añade un mod a
mods.requiredy sus archivos llegan comoadded; elimina uno y sus archivos se leen comomissing. Ese es el requisito (el conjunto declarado es parte de lo que se construye la capa) pero parece que el índice se quedó obsoleto cuando lo que realmente cambió fue el perfil.coresiempre incluye las configuraciones del juego, y eso es la mayor parte de su coste. 70 s con ellas frente a unos 4 s solo para los scripts. No hay interruptor: sin las configuraciones no se puede responder "¿existe una clase de objeto llamada X?", y un segundo eje haría ambigua la medición de desfase -- el recorrido no sabría si esperar los archivosAddons.knowledge_showresponde primero por la capa más cercana. Para una clase que una dependencia reabre conmodded class, la declaración del mod viene antes que la del juego. Ese es el orden correcto y uno sorprendente; pasalayer='core'para la del propio juego.La compilación condicional se indexa, no se resuelve. El 4,9% de las líneas de script del juego están dentro de
#ifdef, incluyendo alrededor de un centenar de declaraciones de clase. Este servidor impulsa compilaciones de servidor, cliente y diagnóstico, por lo que no hay un único conjunto correcto de definiciones: todo se indexa y la guarda se registra en la declaración. Por lo tanto, se puede informar de un nombre que una compilación particular excluye -- la alternativa, filtrar por una suposición de las definiciones, negaría la existencia de métodos que están en la compilación en ejecución.La huella de C12 lleva el tamaño del archivo, y el tamaño de
binarizeno es estable. Reconstruir un modelo que nadie editó produjo un artefacto un byte más grande que el enviado, con el mismo tipo, el mismo recuento de LOD y las mismas cincuenta cadenas -- y un digesto diferente, porque el tamaño es parte de él. Así que C12 puede avisar de una reconstrucción que no cambió nada. Avisa en lugar de negarse exactamente por esta razón, y las partes de las que se construye se informan junto a él para que la comparación pueda hacerse a mano. Dividir el digesto en una mitad estable y un tamaño es el refinamiento obvio y no está hecho.Una exportación parcial avisa; no se niega. Con los argumentos predeterminados propios del exportador, un modelo salió con 2 de sus 5 LOD y pasando todas las demás comprobaciones. Este servidor no pasa esos argumentos, por lo que no debería ocurrir -- pero un objeto marcado como LOD y no enlazado en la escena cuenta en un lado de la comparación y no en el otro, lo que es una razón legítima para que los recuentos difieran, por lo que una negativa tendría falsos positivos. Lee E3.
La regla de contención no puede ver toda raíz incorrecta. Se niega a una raíz que no contiene la carpeta de prefijo del mod, que es el fallo medido. Una raíz que sí contiene una carpeta con ese nombre -- un repositorio cuyo propio directorio de mod se escribe como el prefijo, por ejemplo -- la pasa, y lo que atrapa ese caso es C10 o C3/C4 una capa más abajo. Medido: apuntado a tal raíz, la compilación se negó, no desplegó nada y dejó el artefacto enviado intacto, pero la negativa vino del trabajo en lugar de la llamada.
asset_exportnecesita el add-on de exportación habilitado en Blender, y no puede instalarlo. Blender se lanza con las preferencias reales del propietario de la máquina, porque iniciarlo con--factory-startupelimina el add-on por completo. Sus otros add-ons se mantienen fuera de la ruta de búsqueda para la ejecución (dos de los instalados aquí alcanzan la red al iniciarse y se les culpa de los fallos), lo que Blender informa como "Add-on not loaded" en el registro -- esa línea es obra de este propio servidor, no un fallo.Una configuración binarizada no tiene cuerpo que mostrar.
knowledge_show(body=True)lee una declaración de vuelta del archivo o archivo comprimido del que se indexó, pero unconfig.bincontiene la forma binaria mientras que el índice contiene lo queCfgConverthizo de ella. La respuesta lo dice en lugar de devolver nada.
Instalación
python -m pip install -e ".[dev]"
python -m pytestRegístralo en tu cliente MCP:
{ "mcpServers": { "dayz": { "command": "dayz-mcp" } } }Licencia
GPL-3.0-or-later. Ver NOTICE.md.
Maintenance
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceAn MCP server for Arma Reforger and Enfusion engine modding that enables users to create mods, search API classes, and generate scripts through natural language. It provides a comprehensive suite of tools for scaffolding addons, generating prefabs, and building projects using the Workbench CLI.1,74214
- AlicenseAqualityCmaintenanceMCP server for DayZ Enforce Script that gives AI coding assistants deep knowledge of the DayZ scripting API with semantic search, code validation, class hierarchy, and reverse call graphs.81MIT
- AlicenseAqualityAmaintenanceAn MCP server that empowers AI coding agents to work effectively with Minecraft mod development, providing static analysis of decompiled source code and runtime interaction with a running Minecraft instance.313913MIT
- FlicenseNot gradedqualityCmaintenanceMCP server that helps AI agents inspect Minecraft project evidence (crash logs, mod files, datapacks) before writing development code.3
Related MCP Connectors
An MCP server that gives your AI access to the source code and docs of all public github repos
A MCP server built for developers enabling Git based project management with project and personal…
Augments MCP Server - A comprehensive framework documentation provider for Claude Code
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/covalschi/dayz-agentic-modding-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server