¿Qué hace lan-direct?
Enruta el tráfico de la aplicación a través del ISP mientras tú, el agente, permaneces en la VPN.
Funciona tanto en Claude Code como en Codex: el mecanismo es el enlace por socket, que no tiene nada que ver con el andamiaje.
El invariante
Nunca modifiques la tabla de enrutamiento, la configuración del proxy WinINET/del sistema ni la configuración del cliente VPN. Solo:
- establece variables de entorno limitadas a un proceso hijo, o
- pasa banderas de enlace de fuente por herramienta.
Ambos son por socket y optativos, por lo que no pueden afectar tu propia conexión. Este es el punto central del skill: si usas route add, lo has roto (consulta la sección Salida de emergencia al final).
No sugieras desconectar la VPN. Ese es el problema que este skill existe para evitar.
Flujo de trabajo
1. Detectar y verificar
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\lan-detect.ps1
En Linux o macOS usa la contraparte en su lugar: el mismo JSON, el mismo contrato de código de salida:
bash scripts/lan-detect.sh
Emite JSON: lanIp, gateway, lanDns, subnet, tunnelActive, vpnAdapter, directEgress, lanEgress, fakeIpDns, bypassWorks. Sale con 0 solo cuando bypassWorks es verdadero: demuestra la omisión con dos sondas en vivo en lugar de inferirla.
tunnelActive: false-> no hay VPN activa. Dilo y detente; no hay nada que omitir.bypassWorks: falsecon un túnel activo -> ve a Solución de problemas más abajo. No continúes. El tráfico permanecería silenciosamente en la VPN y reportarías un falso positivo.
2. Elegir el mecanismo
¿Backend Node (incl. Next.js)? Omite el proxy - salta a la receta Node en Recetas por stack. Una variable de entorno, sin proceso proxy, y es lo único que llega al fetch nativo.
Todo lo demás: inicia el proxy vinculado a la LAN. Un proceso servidor no tiene una bandera --interface, así que esta es la forma de obtener una salida al ISP.
node scripts/lan-proxy.mjs --bind <lanIp> --port 8899 # ejecutar en segundo plano
node scripts/lan-proxy.mjs --bind <lanIp> --port 8899 --dns <lanDns> # si fakeIpDns: true
Escucha solo en 127.0.0.1 y registra cada host que tuneliza, para que puedas ver lo que la aplicación realmente llamó.
3. Reiniciar el backend apuntándolo al proxy
HTTP_PROXY=http://127.0.0.1:8899
HTTPS_PROXY=http://127.0.0.1:8899
NO_PROXY=localhost,127.0.0.1,<subnet>
NO_PROXY importa: sin él, las llamadas del backend a sus propios servicios locales se enviarían a través del proxy sin razón. (Las bases de datos locales en TCP - MySQL, Postgres, Mongo - no son HTTP y no se ven afectadas de todos modos).
Establece estas variables en el comando de lanzamiento del backend, no de forma global. Consulta Recetas por stack más abajo - HTTP_PROXY no es suficiente para varios stacks.
4. Ejercitar y confirmar
Golpea el endpoint cuya llamada saliente estaba fallando. Confirma ambos:
- la llamada ahora tiene éxito, y
- el registro del proxy muestra el host al que llamó.
Una prueba exitosa sin una línea de registro coincidente significa que la solicitud nunca pasó por el proxy.
5. Finalizar
Detén el proxy, reinicia el backend sin las variables de entorno. Nada más que deshacer: no se cambió ningún estado global.
pkill -f lan-proxy desde Git Bash no mata un node.exe de Windows. Usa:
Get-CimInstance Win32_Process -Filter "Name='node.exe'" |
Where-Object { $_.CommandLine -like '*lan-proxy*' } |
ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
Una instancia sobrante es inofensiva, pero hará que el próximo lanzamiento falle con un conflicto de puertos.
Recetas por stack
| Stack | Cómo alimentarlo con el proxy |
|---|---|
Python requests / httpx | variables de entorno, tal cual |
Go, .NET HttpClient | variables de entorno, tal cual |
| PHP CLI | libcurl lee http_proxy / https_proxy del entorno del proceso |
| Node (incl. Next.js) | no el proxy - usa la precarga lan-bind.mjs, ver más abajo |
| PHP bajo Apache/XAMPP | ver más abajo - el servicio no hereda tu shell |
| curl, wget, npm, ssh, git | omite el proxy, enlaza directamente (ver Enlace directo más abajo) |
Node - usa la precarga, no el proxy
El fetch nativo se ejecuta sobre el undici integrado de Node, que ignora HTTP_PROXY en Node <= 23 y no acepta https.Agent. Verificado en Node 22.20: las variables de entorno simples, NODE_USE_ENV_PROXY=1 y --use-env-proxy no hacen nada: el registro del proxy permanece vacío y el tráfico sale silenciosamente a través de la VPN. Esto afecta a Next.js, el SDK de openai y cualquier otra cosa que use fetch nativo.
Omite el proxy por completo y usa la precarga, que enlaza cada socket saliente en el proceso:
LAN_BIND=<lanIp> node --import "file:///<directorio-del-skill>/scripts/lan-bind.mjs" script.js
LAN_BIND=<lanIp> NODE_OPTIONS="--import file:///<directorio-del-skill>/scripts/lan-bind.mjs" npm run dev
<directorio-del-skill> es el directorio base de este skill con barras diagonales - normalmente C:/Users/<tu>/claude/skills/lan-direct bajo Claude Code, o C:/Users/<tu>/.agents/skills/lan-direct bajo Codex. --import necesita una URL file:// absoluta en Windows; una ruta de Windows desnuda con barras invertidas falla.
NODE_OPTIONS se propaga a los workers bifurcados, por lo que cubre servidores de desarrollo Next.js y Vite.
Esto llega al fetch nativo, al SDK de openai, a axios, node-fetch, got y a http/https simple a la vez: sin dependencia, sin proceso proxy, sin necesidad de HTTP_PROXY. Omite los destinos de loopback, por lo que una base de datos local o un servidor de desarrollo siguen funcionando.
Solo si la precarga no está disponible: Node >= 24 honra NODE_USE_ENV_PROXY=1 con las variables de entorno, y axios las honra en cualquier versión.
PHP bajo Apache / XAMPP
SetEnv/PassEnv en httpd.conf solo puebla $_SERVER; libcurl lee el entorno del proceso, por lo que no los recogerá. Usa uno de:
- iniciar Apache desde un shell que ya tenga
http_proxy/https_proxyexportadas (p. ej.xampp\apache_start.bat), o putenv('https_proxy=http://127.0.0.1:8899');antes decurl_init(), ocurl_setopt($ch, CURLOPT_PROXY, '127.0.0.1:8899');directamente.
file_get_contents() no honra ninguna de estas: necesita un contexto de flujo con 'proxy' => 'tcp://127.0.0.1:8899'.
Enlace directo
No se necesita proxy cuando la herramienta puede enlazar su propio socket:
| Herramienta | Bandera |
|---|---|
| curl | --interface <lanIp> |
| wget | --bind-address=<lanIp> |
| npm | --local-address=<lanIp> |
| ssh | -b <lanIp> |
| Chromium / Playwright | no existe bandera de enlace - usa el MCP del navegador en el puerto 8900, ver más abajo |
MCP de navegador / Playwright
Chromium no tiene una bandera de enlace de fuente, por lo que un navegador llega al ISP solo a través del proxy. Usa lan-browser.mjs: es un servidor MCP de Playwright que inicia y posee su propio proxy, por lo que el navegador nunca puede iniciarse sin uno.
Se incluye con este skill - scripts/install.ps1 lo registra:
powershell -NoProfile -ExecutionPolicy Bypass -File <directorio-del-skill>\scripts\install.ps1
Qué hosts usan el ISP proviene de tres fuentes combinadas, por lo que casi nunca es necesario reinstalar: lan-hosts.txt en el directorio del skill (valores predeterminados incluidos, reemplazados en git pull), ~/.lan-direct-hosts (los propios del usuario, nunca sobrescritos), y --lan-hosts a,b al iniciar. Una regla por línea, comentarios # permitidos, coincidencia de sufijo.
Cuando un usuario quiera que otro host sea enrutado a través del ISP, añade una línea a ~/.lan-direct-hosts y diles que reinicien el navegador - no edites lan-hosts.txt, que la próxima actualización sobrescribirá.
Si el usuario pide registrarlo manualmente, ten en cuenta que -- debe sobrevivir al enlazador de parámetros de PowerShell - pasa los argumentos como un array, o ejecútalo desde bash:
# desde un clon
claude mcp add playwright-split --scope user -- \
node <directorio-del-skill>/scripts/lan-browser.mjs --lan-hosts ".ir"
# desde npm - sobrevive al movimiento del checkout, así que prefiero esto cuando el usuario lo tiene instalado
claude mcp add playwright-split --scope user -- npx -y lan-direct browser
Otros subcomandos de CLI, cuando el paquete está instalado: lan-direct detect elige el script de detección correcto para la plataforma, y lan-direct route <host> responde "¿este host iría a través del ISP o del túnel?" sin iniciar un navegador. Úsalo cuando un usuario reporte que un sitio no carga: una regla incorrecta y un sitio caído se ven idénticos desde fuera.
El servidor detecta la IP de la LAN, se niega a iniciarse si lan-detect dice que la omisión está rota, reutiliza un proxy existente si ya está escuchando, y apaga lo que inició cuando el MCP termina. Sus propios registros van a stderr; stdout se deja limpio para JSON-RPC.
Usa el puerto 8900, deliberadamente no el 8899 utilizado para las pruebas de backend anteriores. La reutilización es solo por puerto, por lo que compartir el número permitiría que un proxy de backend que enlace todo capture silenciosamente el navegador y enrute todo su tráfico a través del ISP.
La navegación mixta funciona en un mismo navegador. --lan-hosts hace que el proxy decida por host: los hosts coincidentes salen a través del ISP, todo lo demás queda sin enlazar y toma el túnel. Así que "revisa hoosha y youtube" no necesita cambiar de herramienta - hoosha sale a través del ISP, youtube a través de la VPN, misma sesión. El registro del proxy muestra la decisión en cada línea:
CONNECT lan hoosha.com:443
CONNECT vpn youtube.com:443
Mantén la lista precisa: un host doméstico que falta en ella obtiene el túnel y falla con una respuesta vacía, y un host extranjero añadido erróneamente obtiene el ISP y puede ser filtrado. Cuando un sitio falle de una manera que parezca una conexión muerta, comprueba qué ruta eligió el registro antes de asumir que el sitio está caído.
Por qué una lista explícita en lugar de detección automática: conectarse a un host bloqueado a través del túnel tiene éxito a nivel TCP (medido: connect@14ms) y solo muere ~860ms después, después de que el proxy ya haya respondido 200 Connection Established. La reversión implicaría almacenar en búfer y reproducir el handshake TLS y pagar ese retraso en cada solicitud doméstica.
Playwright enruta el loopback a través del proxy. Chrome normalmente omite localhost, pero Playwright lo anula cuando se establece un proxy, por lo que http://127.0.0.1:3000 llega a lan-proxy.mjs en lugar de ir directo. Verificado en el registro del proxy. La protección de loopback del proxy es lo que mantiene accesibles los servidores de desarrollo locales - sin ella, cada una de esas solicitudes fallaría con EADDRNOTAVAIL. No añadas --proxy-bypass=<-loopback>; no cambia nada aquí y solo confunde el panorama.
Solución de problemas
directEgress es igual a lanEgress con un túnel activo. La VPN está aplicando un kill-switch o una ruta estricta (un filtro WFP que bloquea los sockets enlazados). Arréglało en el cliente VPN, no a su alrededor: desactiva la ruta estricta, o añade una regla de enrutamiento directo/de omisión para los hosts que necesitas. nekoray/sing-box: desactiva strict_route, o añade una regla de salida direct.
En Linux esto también puede ocurrir sin ningún kill-switch: una dirección de fuente por sí sola no anula la tabla de enrutamiento allí como lo hace en Windows, por lo que el enrutamiento de políticas mantiene el tráfico en el túnel. La solución es una regla que envíe el tráfico desde la IP de la LAN a través de la puerta de enlace física - ip rule add from <lanIp> table <n> más una ruta predeterminada en esa tabla. Eso necesita root y es un cambio en todo el sistema, así que explícalo y deja que el usuario decida. Todo lo demás en este skill se mantiene sin privilegios y limitado al proceso.
fakeIpDns: true. El resolver del SO está devolviendo marcadores de posición 198.18.x.x que solo significan algo dentro del túnel. Pasa --dns <lanDns> al proxy para que resuelva a través del DNS propio de la LAN.
La sonda de salida no devuelve nada. No hay conectividad con el host de la sonda. Verifica el enlace, vuelve a ejecutar.
Salida de emergencia
Si un backend genuinamente no puede ser proxyficado (un binario compilado sin soporte de proxy), la única opción restante es una ruta estática específica para el host:
route add <targetIp> mask 255.255.255.255 <gateway> metric 1 # necesita admin
route delete <targetIp> # deshacer, siempre
Esto es global. Saca todos los procesos del túnel para ese destino - incluyéndote a ti, si alguna vez contactas el mismo host. Requiere elevación, sobrevive hasta que se elimina y es fácil de olvidar. Pregunta antes de usarlo, y elimínalo en la misma sesión.