Skip to main content
Glama

game-bridge-mcp

Permite que un agente de IA inicie tu juego, lo conduzca y lea lo que ha sucedido — a través de HTTP, un puerto por instancia.

Tu juego ya sabe todo sobre sí mismo: qué hay en pantalla, dónde está cada entidad, qué comandos acepta. game-bridge-mcp es un servidor MCP que pone eso en manos de un agente — como herramientas que el agente puede llamar, descubiertas desde el juego en ejecución y no codificadas aquí.

agent ──MCP(stdio)──▶ game-bridge-mcp ──HTTP──▶ 127.0.0.1:7820  ← it launched this one
                                      ├───────▶ 127.0.0.1:7801  ← your IDE started this one
                                      └───────▶ 127.0.0.1:7802  ← a colleague's session

Tres cosas hacen que sea algo más que un script puente de depuración que escribirías en una tarde:

  • Lanza instancias y elige sus puertos. Ningún llamador elige nunca un puerto ni escribe un comando de compilación, así dos agentes no pueden colisionar, y el puente limpia lo que ha iniciado — sin ventanas de juego huérfanas al terminar una sesión.

  • Toda herramienta recibe un port. Un único puente controla cada instancia que tengas en ejecución: varios agentes en una máquina, o un agente comparando dos compilaciones una al lado de la otra.

  • La lista de herramientas viene del juego. El puente obtiene GET /tools de cada instancia en tiempo de ejecución, así que un comando de depuración que añadiste esta mañana se puede utilizar esta tarde — sin publicar este paquete, sin reconectar y sin desajuste entre lo que el agente cree que el juego acepta y lo que acepta.

Es independiente del motor. Cualquier lenguaje, cualquier cosa que pueda servir cuatro pequeños endpoints HTTP en localhost. El contrato es corto a propósito.


Inicio rápido

npx @wildware/game-bridge-mcp --help

Regístralo en un cliente MCP — para Claude Code, desde el directorio de tu proyecto:

claude mcp add game-bridge -- npx -y @wildware/game-bridge-mcp

Luego, desde el lado del agente:

launch_instance {}                                 // start a game; the bridge picks the port
list_instances  {}                                 // ...or find one already running
list_toolsets   { "port": 7820 }                   // what can this instance do?
describe_toolset { "port": 7820, "name": "play" }  // exact schemas
call_tool { "port": 7820, "name": "drop", "arguments": { "x": 1.2 } }
stop_instance { "port": 7820 }                     // clean shutdown, not a kill

launch_instance necesita una declaración de lanzamiento. Todo lo demás funciona con cualquier juego que implemente la superficie HTTP, lo haya iniciado este puente o no.


Related MCP server: minecraft-mcp

El contrato

Implementa esto y cualquier agente podrá controlar tu juego a través de este puente, sin que haya código aquí que sepa nada de él. Tiene tres partes, y cada una aporta algo específico:

Parte

Qué aporta

1. La superficie HTTP

Leer y controlar una instancia en ejecución.

2. Autorregistro

Encontrar instancias sin adivinar puertos.

3. La declaración de lanzamiento

Iniciar instancias sin que una persona elija un puerto.

Solo la parte 1 es obligatoria. La parte 2 hace fiable el descubrimiento; la parte 3 hace agradable todo el conjunto.

1. La superficie HTTP

Sirve estos en 127.0.0.1:<port>, donde el puerto proviene de un flag de depuración explícito. Vincula solo a loopback y mantén toda la superficie apagada salvo que el juego se haya lanzado con ese flag: es una superficie de depuración, no un servicio de red.

GET /health — obligatorio

Es la comprobación de actividad e identidad. El descubrimiento lo llama contra cada puerto en un rango, así que debe ser barato.

GET /health
{ "ok": true, "frame": 91422 }

ok: true es lo que marca el puerto como uno de los nuestros. Un puerto que responda HTTP con cualquier otra cosa se le comunica al agente como «algo más ha ocupado este puerto» — un problema distinto, con una solución distinta, de «el juego no se está ejecutando».

frame es un contador que aumenta durante toda la vida del proceso. El puente lo vigila: un frame que va hacia atrás significa que un proceso nuevo está respondiendo en este puerto y el manifiesto de herramientas en caché se descarta automáticamente. Eso es lo que hace invisible para el agente la reconstrucción y la re-ejecución.

GET /state — obligatorio

La instantánea completa: todo lo que un agente pueda querer saber, como JSON. No hay un esquema obligatorio — es tu juego —, pero unos pocos campos convencionales desbloquean funciones del puente:

{
  "frame": 91422,
  "simFrame": 48110,
  "completedCommandId": 17,
  "paused": false,
  "ui": {
    "screen": "GameScreen",
    "elements": [ { "label": "Restart", "visible": true } ]
  },
  "events": [ { "m": "merge:cherry" }, { "m": "click:Restart" } ],
  "game": { "score": 1280, "state": "RUNNING" }
}

Campo

Por qué le importa al puente

frame

Detección de reinicio; la confirmación de respaldo de que un comando se ejecutó.

completedCommandId

La confirmación fuerte de que un comando se ejecutó — ver más abajo.

ui.screen

Lo informa list_instances, de modo que cinco instancias se distinguen de un vistazo.

ui.elements[].label / .visible

Se incluyen en el resumen compacto de get_state.

events[].m

Eventos recientes, devueltos después de cada comando para que el agente vea la consecuencia. También se aceptan cadenas simples.

game

Los campos escalares se incluyen en el resumen. Los objetos y arrays anidados no — ahí es donde viven las listas de entidades de varios megabytes.

Todo lo demás que pongas aquí lo transmite get_state sin tocarlo.

GET /command — obligatorio

GET /command?cmd=spawn&type=cherry&x=-1.5
{ "accepted": true, "commandId": 18, "frame": 91430 }

El nombre del comando se indica con la clave cmd, no name — los comandos suelen tener su propio argumento name, y una clave de consulta duplicada sobrescribiría silenciosamente el comando que se está invocando. Cualquier otro parámetro de consulta es un argumento.

Este endpoint es de tipo «dispara y olvida», y es lo más importante que hay que entender sobre el contrato. Responde desde el hilo HTTP en el momento en que el comando se pone en cola; el comando en sí se ejecuta más tarde, en el hilo del juego. Un cliente que lea /state inmediatamente después lee el mundo de antes de que el comando ocurriera. El test parece inestable; el juego está bien.

El puente se encarga de esto, y la forma en que lo hace es lo que tu juego debería soportar:

  1. GET /command?... → anota el commandId devuelto.

  2. Haz polling de GET /state hasta que completedCommandId >= commandId.

  3. Devuelve ese estado — realmente después de que el comando se ejecutara.

Si tu juego no publica completedCommandId, el puente se reduce a esperar que frame avance en dos y etiqueta el resultado como "confirmation": "frames-advanced", para que el agente sepa que obtuvo la garantía más débil. Publicar completedCommandId son unas pocas líneas y merece la pena:

// game thread, once per frame
while (true) {
    val cmd = queue.poll() ?: break
    apply(cmd)
    completedCommandId = cmd.id   // published in the next /state snapshot
}

Se recomienda encarecidamente un comando close que apague el juego mediante su cierre normal: permite que un agente termine una instancia sin matar un proceso. El puente trata close de forma especial — nunca espera una finalización que no puede llegar, espera a que el puerto quede en silencio.

GET /tools — opcional, pero esta es la parte buena

El manifiesto: qué se le puede pedir a tu juego, con sus propias palabras.

{
  "game": { "name": "Orbital Freight", "version": "0.9.2", "protocol": 1 },
  "toolsets": [
    {
      "name": "play",
      "description": "Drive the game the way a player does.",
      "tools": [
        {
          "name": "drop",
          "description": "Release the held crate, aiming first if x is given.",
          "args": [
            { "name": "x", "type": "number", "description": "World x, -2..2", "required": true, "default": null },
            { "name": "settle", "type": "boolean", "description": "Wait for the stack to settle", "default": "true" }
          ]
        }
      ]
    }
  ],
  "passthrough": {
    "description": "Any command the debug bridge accepts, passed straight through.",
    "examples": ["set_seed { seed }", "set_gravity { x, y }"]
  }
}

Campo

Significado

game.name, game.version

Identidad. Lo muestra list_instances, que es como un agente distingue cinco instancias en ejecución.

game.protocol

Versiona el documento, no el conjunto de comandos. Añadir un comando no cambia nada aquí; reestructurar el manifiesto sí. Permite a un puente distinguir «no puedo leer esto» de «este juego conoce comandos distintos de los de la última vez». Versión actual: 1.

toolsets[]

Grupos nombrados por lo que quien llama intenta hacer, no por cómo está organizado tu código. Mantenlos pocos y evidentes.

tools[].name

Lo que invoca el agente.

tools[].description

Escrito para el agente. Di qué hace y cuándo recurrir a él — este es el texto sobre el que el modelo razona.

tools[].args[]

{ name, type, description, required, default }. type es un nombre de tipo de JSON Schema, así que un mismo conversor sirve tanto para los comandos del juego como para las herramientas del puente.

tools[].command

El cmd que se debe enviar, si difiere del nombre de la herramienta. Por defecto, el nombre.

tools[].sync

false para comandos en los que no se debe esperar. Por defecto, true.

tools[].inputSchema

Si ya tienes JSON Schema, envíalo en lugar de args y se usará tal cual.

passthrough

Texto libre y ejemplos, que describe comandos que no has publicado formalmente.

Los valores por defecto pueden ser cadenas ("true", "0.05") — un manifiesto serializado desde un lenguaje tipado normalmente los representa así. El puente los integra en la descripción en lugar de emitir default en el esquema, porque default: "false" en una propiedad boolean es algo que un cliente estricto podría rechazar. null significa «sin valor por defecto».

El analizador es deliberadamente tolerante, porque un manifiesto se escribe con el serializador que ya se tenga a mano:

  • toolsets puede ser un array de objetos o un mapa de name → toolset.

  • los argumentos pueden estar bajo args, arguments o params, como un array de objetos, un array de nombres simples, o un mapa de name → { type, description }.

  • Una herramienta malformada se descarta, no es fatal. Una entrada defectuosa no debe dejar fuera de línea una instancia entera.

Si /tools devuelve 404, no se rompe nada. El puente recurre a un manifiesto integrado que contiene solo las herramientas de nivel de contrato y le dice al agente que el juego no publica una lista de comandos, así que debe trabajar mediante raw_command y leer /state. Las instancias se notifican como live o live-no-manifest según corresponda, y --manifest ./my-game.json aporta uno desde un archivo para un juego que no puedes cambiar.

2. Autorregistro

El escaneo de puertos es la forma débil de descubrimiento: limitado por un rango que alguien adivinó, silencioso sobre la identidad de un juego hasta que responde, y propenso a falsos negativos durante el arranque — justo el momento en que un agente tiene más probabilidades de buscar.

Por eso, un juego que ha enlazado con éxito su puerto de depuración escribe un pequeño archivo JSON que dice su nombre:

~/.game-bridge/instances/<pid>.json
{
  "name": "Orbital Freight",
  "version": "0.9.2",
  "protocol": 1,
  "port": 7820,
  "pid": 12345,
  "host": "127.0.0.1",
  "started": "2026-08-20T22:27:19.774Z",
  "cwd": "/home/dev/checkouts/main"
}

cwd es deliberado: varias copias del mismo juego se ejecutan a la vez, y «¿qué compilación es esta?» no se puede responder de otro modo desde fuera del proceso.

Las reglas que debe seguir quien escribe:

  • Escribe la entrada después de que el puerto esté vinculado, nunca antes. Una entrada para un puerto que nunca fue reclamado es peor que ninguna entrada.

  • Bórrala en el apagado limpio.

  • Nunca dejes que un fallo del registro rompa el juego. Un directorio sin permiso de escritura, un home de solo lectura, un sandbox: el juego debe seguir arrancando y seguir sirviendo sus endpoints. Esto es publicidad, no infraestructura.

  • Respeta GAME_BRIDGE_INSTANCES (el directorio de entradas) o GAME_BRIDGE_HOME (su padre) si alguno está definido.

Las reglas que un lector debe seguir — y estas importan más:

  • Las entradas son orientativas, nunca autoritativas. Un cuelgue o un cierre forzado deja el archivo atrás. Esto ocurre constantemente en la práctica.

  • Verifica cada entrada con GET /health antes de creértela. Una entrada cuyo puerto no responde es un archivo obsoleto, no un juego en ejecución, y debe informarse como tal en lugar de como una instancia.

  • Nunca confíes en una entrada por encima del juego en vivo. El bridge toma el nombre y la versión de /tools cuando el juego responde, y usa la entrada solo para lo que el cable no puede decir: pid, directorio de trabajo, hora de inicio.

  • No borres los archivos de otros procesos por defecto. Un juego que aún está vinculando su puerto es indistinguible de uno que se ha colgado durante un segundo o dos. El bridge poda solo con un prune: true explícito, y solo después de verificar que el puerto está muerto.

  • Sigue escaneando también. Existen juegos anteriores al registro; el bridge fusiona las entradas del registro y un escaneo de puertos y deduplica por puerto, informando el discovery de cada instancia como registry, scan o both.

3. La declaración de lanzamiento

Un proyecto declara una vez cómo se inicia, para que ningún llamador tenga que escribir un comando de compilación o elegir un puerto. Pon gamebridge.json en la raíz del proyecto — el bridge sube desde su directorio de trabajo para encontrarlo, como cualquier otra herramienta JS encuentra su configuración, y --config <file> lo anula.

{
  "name": "Orbital Freight",
  "launch": {
    "command": "./gradlew lwjgl3:run -PdebugPort={port} --console=plain",
    "cwd": ".",
    "portRange": "7820-7839",
    "readyTimeoutMs": 180000,
    "env": { "ORBITAL_DEV": "1" }
  }
}

Campo

Significado

command

Línea de comandos de shell. Se sustituye {port}. El puerto debe llegar al juego desde aquí — ese es el mecanismo completo.

argv

Alternativa a command: ["./run-game", "--port", "{port}"], ejecutado sin shell.

cwd

Directorio de trabajo, resuelto relativo a este archivo — no al lugar donde el cliente MCP haya iniciado el bridge, que casi nunca es el proyecto.

portRange

Puertos que el lanzador puede reclamar. Por defecto 7820-7839, deliberadamente lejos de 7777 y 7800-7810, que son los puertos que la gente asigna a mano.

readyTimeoutMs

Cuánto esperar por /health. Un build en frío más una JVM son decenas de segundos; el valor por defecto es 180000.

env, extraArgs

Entorno adicional y argumentos finales.

Lo que el lanzador garantiza entonces:

  • El puerto se verifica libre dos veces — nada vinculado a él, y nada respondiendo a una comprobación de salud en él — porque un juego a mitad de arranque ha reclamado el puerto de la manera que importa mientras aún falla una prueba de vinculación milisegundos antes. Si el rango declarado está lleno, cae a un puerto asignado por el SO.

  • launch_instance devuelve solo cuando /health responde, así que un llamador nunca escribe un bucle de reintentos.

  • Un fallo de arranque falla en voz alta, con la salida del propio hijo. Cuando un juego no arranca, el stack trace es toda la respuesta:

    BridgeUsageError: Launch failed on port 7820: the process exited with code 1.
    Command: ./gradlew lwjgl3:run -PdebugPort=7820 --console=plain
    Working directory: /home/dev/orbital
    Full log: /tmp/game-bridge-logs/instance-7820-1787264781573.log
    
    Last output:
    'gradlew' is not recognized as an internal or external command,
    operable program or batch file.
  • stdout y stderr se capturan en ese archivo de registro durante la vida de la instancia, y las últimas 200 líneas se mantienen en memoria para instance_log.

  • Los hijos se recolectan. En stop_instance, y en el apagado del servidor, SIGINT, SIGTERM o la desconexión del cliente, cada instancia lanzada se cierra — primero el comando close del propio juego, luego la terminación del árbol de procesos si no quiere irse. Una sesión cerrada nunca deja ventanas de juego en el escritorio.

La escalada más allá de un cierre limpio se aplica solo a los procesos que este bridge ha lanzado y sigue rastreando. stop_instance en cualquier otro puerto se niega y te dice que pidas al juego que se cierre solo.


Herramientas

Herramienta

Argumentos

Qué hace

launch_instance

port?, timeoutMs?

Inicia un juego, elige un puerto libre, espera /health, devuelve el handle.

list_instances

range?, registry?, scan?, prune?

Registro más escaneo de puertos. Nombres, versiones, pids, directorios de trabajo, pantallas. Solo lectura.

stop_instance

port?, graceMs?

Cierre limpio, luego escalera — solo para instancias que este bridge lanzó.

instance_log

port?, lines?

stdout/stderr capturados de una instancia lanzada.

list_toolsets

port?

Los toolsets de esa instancia, tal como se describe a sí misma.

describe_toolset

port?, name

JSON Schemas completos. bridge para las herramientas del propio bridge, passthrough para comandos no publicados.

call_tool

port?, name, arguments?

Ejecuta una herramienta, espera a que el juego la confirme, devuelve el resumen resultante.

Solo estos siete se anuncian. Las herramientas del propio juego se alcanzan a través de call_tool, porque un cliente MCP recibe la lista de herramientas una vez, cuando se conecta, y nunca vuelve a preguntar — una lista fija estaría obsoleta para un juego que aún se está escribiendo y simplemente incorrecta cuando una sesión está manejando dos builds diferentes. (--eager aplana todo por adelantado para clientes que no pueden recorrer un camino de descubrimiento.)

El toolset del propio bridge

Proporcionado para cada juego conforme, sea lo que sea:

Herramienta

Qué hace

get_state

La instantánea completa de /state, o summary: true para un resumen compacto.

get_health

Liveness y contador de frames.

raw_command

Cualquier comando por nombre, publicado o no.

wait_for

Consulta /state hasta que un campo alcanza un valor, un campo cambia, o aparece un evento. Así es como esperas lo que el comando que lo inició no puede informar — una captura de pantalla en cola que llega al disco, una simulación que llega al frame N.

close

Apagado limpio; el puerto quedándose en silencio es la confirmación.

Cómo resuelve call_tool un nombre

  1. Los compuestos del bridge, incluidos los que una aplicación host haya registrado. Un compuesto sombrea un comando del juego con el mismo nombre, lo que siempre es una mejora en lugar de una sorpresa: un compuesto lleva ese nombre precisamente porque el comando crudo devuelve antes de que haya ocurrido lo que pidió.

  2. El manifiesto del juego — con una re-búsqueda en caso de fallo, para que un juego reconstruido con nuevos comandos se recoja a mitad de sesión.

  3. Passthrough — cualquier otra cosa se envía como comando crudo. Un comando que existe en el juego pero no en su manifiesto sigue funcionando hoy. Si el juego lo rechaza como desconocido, obtienes la lista de lo que sí acepta.

Cómo se resuelve port

Cada herramienta acepta un port opcional (en el nivel superior o dentro de arguments). Se resuelve en este orden:

  1. el port explícito en la llamada,

  2. --port en la línea de comandos,

  3. GAME_BRIDGE_PORT en el entorno,

  4. 7777.

Así que una configuración de una sola instancia nunca piensa en puertos, y una sesión de múltiples instancias nunca necesita un segundo servidor.


Manejando dos instancias a la vez

El escenario para el que se construyó esto: dos builds del mismo juego, lado a lado, un agente, una sesión.

// 1. What is already running?
list_instances {}
{
  "registryDir": "/home/dev/.game-bridge/instances",
  "live": [
    { "port": 7801, "discovery": "scan", "status": "live-no-manifest",
      "frame": 2453, "manifest": "fallback", "screen": "GameScreen" },
    { "port": 7820, "discovery": "both", "status": "live", "game": "Orbital Freight",
      "version": "0.9.2", "protocol": 1, "manifest": "game", "pid": 87488,
      "cwd": "/home/dev/checkouts/main", "screen": "MenuScreen",
      "toolsets": ["play", "build", "flow", "bridge"], "launchedByThisBridge": true }
  ],
  "stale": [],
  "notAGame": [],
  "free": [7777, 7802, 7803]
}

El puerto 7801 es un build más antiguo sin /tools: sigue siendo totalmente manejable, solo que no se autodescribe. El puerto 7820 es uno que este bridge inició.

// 2. Start a second one. You do not choose the port.
launch_instance {}
{ "port": 7821, "pid": 90114, "name": "Orbital Freight", "version": "0.9.3-rc1",
  "cwd": "/home/dev/checkouts/rc", "readyInMs": 4080,
  "logFile": "/tmp/game-bridge-logs/instance-7821-1787264835950.log" }
// 3. Same seed, same move, both runs.
call_tool { "port": 7820, "name": "set_seed", "arguments": { "seed": 12345 } }
call_tool { "port": 7821, "name": "set_seed", "arguments": { "seed": 12345 } }

call_tool { "port": 7820, "name": "drop", "arguments": { "x": 1.2 } }
call_tool { "port": 7821, "name": "drop", "arguments": { "x": 1.2 } }

Cada uno devuelve el estado después de que se aplicó el comando, así que los dos son directamente comparables:

{
  "port": 7821, "tool": "drop", "via": "manifest", "command": "drop",
  "applied": true, "commandId": 18, "confirmation": "completedCommandId",
  "frame": 948, "screen": "GameScreen",
  "game": { "score": 1280, "state": "RUNNING" },
  "events": ["merge:cherry", "score:+40"]
}
// 4. Wait for something the command could not report.
call_tool { "port": 7821, "name": "wait_for",
            "arguments": { "path": "game.pendingMerges", "equals": 0, "timeoutMs": 5000 } }

// 5. Clean up what you started. 7801 is not yours - leave it alone.
stop_instance { "port": 7821 }
{ "port": 7821, "stopped": true, "how": "closed cleanly" }

Cuando algo va mal

El bridge distingue los fallos que parecen idénticos desde fuera:

GameOffline: No game is answering on http://127.0.0.1:7809.
Start one with:
  ./gradlew lwjgl3:run -PdebugPort=7809

NotAGameSurface: Something is listening on http://127.0.0.1:7802, but it is not a
debuggable game: GET /health returned HTTP 404.
A drivable game must answer GET /health with {"ok":true,"frame":N}. Check whether
another process has taken this port.

CommandTimeout: Command 'restart' was queued on port 7801 but was not applied
within 5000ms. The game accepted it, so it is probably blocked, frozen, or on a
screen that ignores this command.

Establece el comando nombrado en el primer mensaje con --launch-hint "make run PORT={port}" (o deja que launch_instance haga el inicio).


CLI

npx @wildware/game-bridge-mcp [options]

  -p, --port <n>           Default port for tools that do not name one (default 7777)
      --scan-range <spec>  Ports list_instances sweeps (default 7777,7800-7810)
      --no-scan            Discover only via the instance registry
      --no-registry        Discover only by scanning ports
      --registry-dir <dir> Where instance entries live (default ~/.game-bridge/instances)
      --config <file>      Project launch declaration (default: nearest gamebridge.json)
      --launch-hint <cmd>  Command shown when a port is dead; {port} is substituted
      --manifest <file>    Tool manifest for games that do not serve GET /tools
      --eager              Advertise every tool flatly, for clients that cannot discover
      --timeout <ms>       HTTP and command timeout (default 5000)
  -h, --help
  -v, --version

Entorno: GAME_BRIDGE_PORT, GAME_BRIDGE_SCAN_RANGE (o GAME_BRIDGE_SCAN), GAME_BRIDGE_LAUNCH_HINT (o GAME_BRIDGE_LAUNCH), GAME_BRIDGE_MANIFEST, GAME_BRIDGE_CONFIG, GAME_BRIDGE_INSTANCES, GAME_BRIDGE_HOME.

Todo lo que el bridge registra va a stderr; stdout es el transporte MCP, y una línea suelta en él corrompe el flujo del protocolo.


Usándolo desde tu propio proyecto

Las piezas se exportan además de enviarse como CLI. Si tu juego necesita herramientas que encadenan varios comandos — "soltar, luego esperar a que el tablero se asiente, luego informar el delta de puntuación" — regístralas como compuestos y hereda el protocolo, el lanzador, el descubrimiento y los mensajes de error en lugar de mantener una segunda copia de ellos.

#!/usr/bin/env node
import { parseCli, applyProjectConfig, startStdioServer } from "@wildware/game-bridge-mcp";

const { config } = parseCli(process.argv.slice(2), process.env);
await applyProjectConfig(config);

await startStdioServer(config, {
  composites: [
    {
      name: "drop_and_settle",
      description: "Drop at world x and wait until nothing is moving. The main way to play.",
      only: "Orbital Freight",            // never offered to a game that has no crates
      args: [{ name: "x", type: "number", required: true, description: "World x" }],
      async run(ctx) {
        const before = await ctx.state();
        await ctx.commandAndSync("drop", { x: ctx.args.x });
        const settled = await ctx.call("wait_for", { path: "game.moving", equals: 0, timeoutMs: 10000 });
        const after = await ctx.state();
        return { settled: settled.matched, scoreDelta: after.game.score - before.game.score };
      },
    },
  ],
});

Un compuesto recibe un contexto limitado a una instancia — state, health, command, commandAndSync, call (cualquier otra herramienta), manifest, summarise, sleep — así que nunca tiene que pensar en puertos. only nombra los juegos para los que sirve, comparado con el game.name del manifiesto; un bridge que dice ser genérico no debe ofrecer drop_and_settle a un simulador de vuelo.

La regla para lo que pertenece a un compuesto: o encadena varios comandos o espera algo que /command no puede informar. Cualquier cosa que sea un comando con un conjunto de argumentos pertenece al manifiesto del propio juego, donde se mantiene en sintonía con el código que lo implementa.

Piezas de nivel inferior — Bridge, GameClient, Launcher, readRegistry, normaliseManifest — también se exportan. Bridge y GameClient aceptan un fetchImpl opcional, que es como la suite de pruebas maneja todo el asunto sin un juego o un socket.

Desarrollo

npm install
npm run build     # TypeScript -> dist/
npm test          # builds, then runs node --test

81 pruebas, ninguna de las cuales necesita un juego en ejecución: orden de resolución de puertos, caché de manifiesto y sus tres rutas de invalidación, el respaldo 404 de /tools, el ciclo de comando/encuesta/confirmación y su degradación de marcos avanzados, lectura de registro con entradas obsoletas y malformadas, selección de puerto del lanzador, fallo de arranque y recolección de procesos hijos, y la propia superficie MCP ejecutada a través de un transporte en memoria.

Publicación

Aún no publicado en npm. Cuando lo esté:

npm version minor          # keep SERVER_VERSION in src/server.ts in step
npm test                   # prepublishOnly runs build + test again
npm pack --dry-run         # confirm dist/, README.md and LICENSE are the payload
npm publish                # publishConfig.access is already "public"

files en package.json limita el tarball a dist/, README.md y LICENSE; prepare compila al instalar desde git, por lo que una dependencia instalada desde git funciona sin un dist/ incluido en el repositorio.

Licencia

MIT — consulte LICENSE.

Available Tools

7 tools
call_toolA

Run a tool on one instance and return the result. Accepts any tool from any of that instance's toolsets, and any raw debug command it understands even if unpublished. Commands are waited on until the game reports them applied, so the state returned is the state after the command ran.

NameRequiredDescriptionDefault
nameYesTool or command name
portNoDebug port of the game instance to talk to. Defaults to the bridge's --port, then GAME_BRIDGE_PORT, then 7777. launch_instance returns one; list_instances finds the rest.
argumentsNoArguments for the tool

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden. It discloses that it accepts unpublished raw debug commands, runs on one instance, waits until the game reports the command applied, and returns the state after execution. This gives an agent crucial insight into side effects and synchronization behavior beyond the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two focused sentences with no filler. The primary purpose is front-loaded, and the important behavioral qualifiers about command applicability and waiting semantics are delivered compactly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is largely complete for a generic dynamic executor, especially given the schema documents parameters. It covers purpose, target scope, command breadth, and post-command state. It does not enumerate return-value shapes or possible errors, but that is difficult for arbitrary tools and is not a major gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds meaningful semantics to the 'name' parameter by explaining it can be any tool from the instance's toolsets or any raw debug command, even unpublished. It also implicitly ties 'port' to 'one instance' and clarifies that arguments are forwarded to the selected tool or command.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: 'Run a tool on one instance and return the result.' It clearly distinguishes call_tool from the lifecycle/discovery siblings (launch_instance, list_instances, stop_instance, instance_log) by framing it as the generic executor for any tool or command.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage context instead of stating it explicitly: use this tool when you need to run any tool or raw debug command against a single instance. However, it never mentions alternatives or calls out when not to use it, such as preferring instance_log for log retrieval or launch_instance for starting a game.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

describe_toolsetA

Full schemas for the tools in one toolset on one instance. 'bridge' is the toolset this bridge adds itself; 'passthrough' explains how to send commands the game has not published.

NameRequiredDescriptionDefault
nameYesToolset name, 'bridge', or 'passthrough'
portNoDebug port of the game instance to talk to. Defaults to the bridge's --port, then GAME_BRIDGE_PORT, then 7777. launch_instance returns one; list_instances finds the rest.

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must carry behavioral disclosure. It implies a read-only introspection operation by saying it provides 'full schemas' and 'explains' commands, which is reasonably transparent. It does not mention error behavior, whether a running instance is required, or that it makes no modifications, but the read-only nature is strongly implied.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact, with the core purpose front-loaded in the first sentence and essential special-case terminology in the second. Every sentence contributes useful information and there is no filler or repetition of schema content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple introspection tool with two parameters and full schema coverage, the description covers the essential semantics including special name values and instance scoping. It does not describe the exact return structure, but 'full schemas' sufficiently conveys the expected output for this tool type. Missing explicit usage guidance vs siblings is the main gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The tool description adds genuine value beyond the schema by explaining what 'bridge' and 'passthrough' mean for the name parameter, which the schema only lists as allowed values. Port semantics are already fully covered in the parameter description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states that the tool returns full schemas for all tools in a toolset on a single instance, which is a specific, recognizable purpose. It also distinguishes itself from siblings like list_toolsets by emphasizing schema detail and instance scoping. The special values 'bridge' and 'passthrough' add useful differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The intended use is implied: if an agent needs full tool schemas, this is the tool. However, it does not explicitly contrast with list_toolsets, call_tool, or other siblings, nor state when not to use it. The explanation of 'bridge' and 'passthrough' gives usage context but not alternative-based selection guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

instance_logA

The captured stdout/stderr of an instance this bridge launched. When a game fails to boot, or dies mid-session, the stack trace is here and nowhere else.

NameRequiredDescriptionDefault
portNoDebug port of the game instance to talk to. Defaults to the bridge's --port, then GAME_BRIDGE_PORT, then 7777. launch_instance returns one; list_instances finds the rest.
linesNoHow many trailing lines (default 50)

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden. It clearly states this is a read-only view of captured stdout/stderr from bridge-launched instances and that the interesting failure information is 'here and nowhere else', giving a strong execution-time mental model. It doesn't mention output formatting or truncation, but the captured-log framing covers the main behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences: the first defines what the tool returns, the second says when to use it. No filler, no repetition of schema details, and the key use case is front-loaded early.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple read-only log tool with two optional parameters and schema-covered parameter semantics, this description plus the schema is nearly complete. It tells the agent what the tool does, which scenarios call for it, and how parameters work. A minor gap is the exact return format, but with no output schema this is not critical.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the parameters port and lines already have clear meaning, defaults, and port resolution order. The description adds no extra parameter-level detail, but the baseline is 3 when schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description identifies the tool's resource: captured stdout/stderr for an instance launched by this bridge, and ties it to boot failures and mid-session crashes. It lacks an explicit verb like 'get' or 'read', but the meaning is unmistakable and it is clearly distinct from the launch/list/stop siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives direct usage context: when a game fails to boot or dies mid-session, the stack trace is here. It doesn't name alternative tools, but among lifecycle/toolset siblings this one is obviously the log-reader, so the guidance is sufficient.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

launch_instanceA

Start a game instance and wait until it answers. The bridge picks a free port itself, so you never choose one and never collide with someone else's instance, and it owns the process: it is reaped when this server shuts down. Uses the project's launch declaration (gamebridge.json). Returns the port to pass to every other tool. Prefer this over starting the game yourself.

NameRequiredDescriptionDefault
portNoForce a specific port instead of letting the bridge choose. Rarely needed.
timeoutMsNoHow long to wait for the game to answer. A cold build plus a JVM can take minutes.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full behavioral burden, and it largely succeeds. It discloses that launch is synchronous ('wait until it answers'), that ports are auto-selected to avoid collisions, that the bridge owns the process and reaps it on shutdown, that it reads the project's gamebridge.json, and that the tool returns a port. These are exactly the behavioral details an agent needs beyond the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Every sentence earns its place. The description is front-loaded with the core action and wait behavior, then explains port auto-selection, process ownership, config source, return value, and provides a usage recommendation. It is compact, well-organized, and free of filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with two optional parameters and no output schema, the description explains the key runtime contract: the launched process lifecycle, how the port is chosen, what the return value is for, and why the tool should be preferred over manual startup. An agent has enough context to select and call the tool correctly, including what to do with the returned port.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already documents both parameters (port and timeoutMs), so baseline is 3. The description adds meaningful context beyond the schema: the bridge automatically picks a free port, so forcing one is rarely needed, and the returned port must be passed to all other tools. This helps an agent understand when and why to use or omit the optional port parameter. Timeout semantics are left mostly to the schema, but the description's 'wait until it answers' supports the intent.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description starts with a clear, specific verb and resource: 'Start a game instance and wait until it answers.' It also distinguishes itself from starting the game manually and, by mentioning the returned port that every other tool needs, it implicitly differentiates from listing/stopping/logging tools. This is unambiguous and not a tautology.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear usage context: it is the preferred way to launch the game, the bridge handles port allocation, and the tool waits until the game responds. It explicitly says 'Prefer this over starting the game yourself,' which is strong guidance. It does not enumerate exact when-not-to-use conditions for each sibling, but the context is sufficient for an agent to choose it appropriately.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_instancesA

What is running: instances that self-registered, plus a sweep of the configured port range for games that predate the registry. Reports name, version, pid, working directory, current screen and whether each was found via the registry or the scan. Read-only, so it is safe to point at instances someone else owns. Use it to attach to a game you did not launch.

NameRequiredDescriptionDefault
scanNoSweep the port range (default true)
pruneNoDelete registry entries whose port has been verified dead. Off by default: a game still binding its port looks identical to a crashed one.
rangeNoPorts to sweep, e.g. '7777,7800-7810'. Defaults to --scan-range.
registryNoRead the instance registry (default true)

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It explicitly discloses the read-only safety property ('Read-only, so it is safe to point at instances someone else owns'), which is the key behavioral trait an agent needs. It does not contradict any annotations since none exist.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with the core purpose ('What is running'), followed by reported fields, then safety and use case. Zero wasted words; every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description usefully enumerates the returned fields and that coverage is complete for a read-only enumeration tool. The only mild gap is that the output format/ordering is unspecified, which is a minor omission for a listing tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline 3 applies even without param detail in the description. The description adds conceptual framing (the port sweep relation to 'games that predate the registry') but the schema already documents scan, prune, range, and registry thoroughly, including the dangerous prune default behavior.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Leads with a specific verb and object — 'What is running: instances that self-registered, plus a sweep of the configured port range'. It names the exact fields reported (name, version, pid, working directory, current screen) and the two discovery sources (registry vs scan), and the read-only framing plus the 'attach to a game you did not launch' use case clearly separates it from the launch/stop/log siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit purpose statement — 'Use it to attach to a game you did not launch' — and explains the two data sources, which signals when a scan is needed (old instances) vs the registry alone. It does not explicitly name when NOT to use it or point to an alternative (e.g. instance_log for logs), but the context is clear enough for an agent to select it for enumeration.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_toolsetsA

List the toolsets one instance publishes, as that instance describes itself. Different ports can legitimately return different lists. Start here, then describe_toolset, then call_tool.

NameRequiredDescriptionDefault
portNoDebug port of the game instance to talk to. Defaults to the bridge's --port, then GAME_BRIDGE_PORT, then 7777. launch_instance returns one; list_instances finds the rest.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the burden of behavioral disclosure. It reveals that the response reflects the instance's self-description and can vary by port, which is meaningful behavioral context. It also implies a read-only listing operation, though it doesn't go deeper into failure cases or return format.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences deliver the purpose, a critical caveat, and the recommended workflow. Every sentence earns its place, and the key distinction is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers the essential workflow, port variability, and the self-describing nature of the response. It doesn't describe the exact return structure, but for a simple list operation without an output schema this is a minor gap given the clear intent and provided next steps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The single 'port' parameter is fully documented in the schema, including defaults and how to obtain a port from sibling tools. The description adds only the note that different ports may return different lists, which supplements but does not significantly expand on the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states a specific verb ('List') and resource ('toolsets one instance publishes') and adds the important qualifier 'as that instance describes itself.' It is immediately distinguishable from sibling tools like launch_instance, list_instances, describe_toolset, and call_tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly instructs the agent to start here and then proceed to describe_toolset and call_tool, giving a clear workflow. It also warns that different ports can return different lists, which is essential context for choosing which instance/port to query.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

stop_instanceA

Shut down an instance this bridge launched: the game's own close command first, then termination of the process tree if it will not go. Only ever applies to a process this bridge started. To ask a game you did not launch to exit, use call_tool { name: 'close' }.

NameRequiredDescriptionDefault
portNoDebug port of the game instance to talk to. Defaults to the bridge's --port, then GAME_BRIDGE_PORT, then 7777. launch_instance returns one; list_instances finds the rest.
graceMsNoHow long to wait for a clean exit

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden, and it delivers: it discloses the two-stage shutdown behavior, the escalation to process-tree termination, and the boundary that only bridge-launched processes are affected. This is exactly the kind of hidden destructive behavior an agent needs to know before invoking.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three compact sentences, with the core action and escalation front-loaded and no filler. Every sentence earns its place; nothing restates the tool name or repeats schema content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a shutdown tool with two optional parameters, clear destructive behavior, explicit scope boundaries, and a named sibling alternative, everything needed to select and invoke it correctly is present. The absence of an output schema is not a practical gap since the agent does not need return-value details to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Both parameters are already fully described in the input schema, including port resolution order and graceMs purpose. The description adds no additional parameter-level detail, but with 100% schema coverage the baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Shut down') and resource ('an instance this bridge launched'), and explicitly contrasts it with call_tool's 'close' command. An agent can immediately tell this tool is for shutdown of bridge-launched instances only.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly scopes the tool to processes this bridge started and gives the exact alternative for games not launched by the bridge: use call_tool { name: 'close' }. This gives the agent both a clear when-to-use and when-not-to-use rule.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 7 tool updatesv0.1.0
    • First observedcall_tool
    • First observeddescribe_toolset
    • First observedinstance_log
    • First observedlaunch_instance
    • First observedlist_instances
    • First observedlist_toolsets
    • First observedstop_instance

TDQS

A4.4/5.0

Scored across 7 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: launch, list, stop, get log, list toolsets, describe toolset, and call tool. No overlapping functionality.

Naming Consistency4/5

Most tools follow the verb_noun pattern (launch_instance, list_instances, stop_instance, list_toolsets, describe_toolset, call_tool), but 'instance_log' is a noun phrase rather than a verb action, which is a minor inconsistency.

Tool Count5/5

Seven tools is well-scoped for a game bridge server, covering management and introspection without being excessive or insufficient.

Completeness5/5

The tool set covers the full lifecycle of game instances (launch, list, stop, log) and toolset interaction (list, describe, call), leaving no obvious gaps for the stated purpose.

Maintenance

ActivityStale
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers