CodeGym /Cursos /ChatGPT Apps /Depuración local: registros, inspección MCP y Dev Mode

Depuración local: registros, inspección MCP y Dev Mode

ChatGPT Apps
Nivel 7 , Lección 1
Disponible

1. Por qué una lección aparte sobre depuración local

En módulos anteriores ya vimos cómo están organizados el stack de Apps SDK y MCP. Ahora hablemos de por qué necesitamos una lección aparte sobre depuración local.

El camino de muchos es: «Bueno, simplemente abriré ChatGPT, escribiré "usa mi App", y luego veré qué dice. Si no funciona, reescribo el código al azar». Es como arreglar el backend mirando solo la página HTML en el navegador y sin abrir nunca los registros del servidor.

Con ChatGPT Apps es especialmente fácil caer en la magia: hay un GPT, decide por sí mismo si llama a una herramienta o no, y tiene su propia lógica de errores. Si no ves lo que ocurre bajo el capó, la depuración se convierte en chamanismo.

Nuestro objetivo: convertirlo en un proceso de ingeniería normal:

  • sabes dónde mirar los registros de Next/MCP;
  • sabes invocar manualmente el servidor MCP a través del inspector;
  • entiendes qué comprueba exactamente Dev Mode y cómo asegurarte de que ChatGPT puede llegar a tu servidor.

Y lo más importante: dejas de depurar con «la adivinanza de GPT» y empiezas por verificar primero los niveles bajos del stack: el servidor y el protocolo, y solo después el UI y el comportamiento del modelo.

2. Modelo mental: tres niveles de depuración

Para no ahogarnos en el caos, acordemos pensar la depuración en términos de tres niveles. Es nuestro pequeño «pastel por capas»:

Nivel Qué contiene Síntomas típicos Cómo depurarlo
UI (widget) Componentes React, estado, window.openai Widget vacío/gris, renderizado defectuoso, los botones no funcionan DevTools del navegador
Backend / servidor MCP herramientas, acceso a BD/API errores 500, «la herramienta falló», datos extraños registros del servidor, MCP Inspector
Protocolo MCP JSON‑RPC, tools/list, tools/call, esquemas GPT escribe «no pudo invocar la herramienta», invalid params inspector + registros de solicitudes

En el segundo nivel nos interesa lo que hace el propio servidor MCP (herramientas, BD, API) y, en el tercero, el «cableado» y el formato de los mensajes MCP (JSON‑RPC, esquemas, etc.).

Este trío es la base del plan de la lección y del curso de depuración.

Para mayor claridad, podemos ver el flujo de una solicitud:

sequenceDiagram
    participant User as Usuario
    participant ChatGPT as ChatGPT (Dev Mode)
    participant Tunnel as Túnel (ngrok/CF)
    participant Next as Next.js + MCP

    User->>ChatGPT: "Elige un regalo de hasta 50 $"
    ChatGPT->>Next: tools/call search_gifts (a través de Tunnel)
    Next->>Next: Invocamos la herramienta MCP, consultamos la BD/API
    Next-->>ChatGPT: JSON-RPC result + ToolOutput
    ChatGPT-->>User: Respuesta + renderizado del widget

Puede fallar en cualquier punto: túnel, endpoint, lógica MCP, esquema JSON, widget de React. Tu tarea al depurar es entender en qué capa está el error, y no reescribir todo de inmediato.

3. Registros de Next.js y MCP: la base de todo

Empecemos por lo más aburrido y más útil: los registros.

Dónde viven los registros en desarrollo local

En la plantilla estándar de Apps SDK sobre Next.js, el servidor MCP suele estar envuelto en una ruta de API (/api/mcp o similar). Ejecutas npm run dev, y en una terminal tienes:

  • el servidor de desarrollo de Next.js;
  • el manejador del endpoint MCP, que recibe solicitudes JSON‑RPC tools/list, tools/call, etc.;
  • la impresión de todo el espectáculo vía console.log/console.error.

Si separaste MCP en otro proceso, habrá una segunda terminal, pero la idea es la misma: lo interesante se ve en la consola.

Importante distinguir:

  • errores de compilación/arranque: no levanta next dev, falla TypeScript, import incorrecto, etc.;
  • errores de ejecución: todo arranca, pero una solicitud concreta a /api/mcp provoca que la herramienta falle.

Next.js en modo dev muestra los errores de runtime con un overlay bonito y además imprime el stack trace en la consola.

Qué registrar en el servidor MCP

Aunque MCP usa el protocolo JSON‑RPC, para depurar no necesitas imprimir absolutamente todo el JSON. Es mucho más útil tener registros estructurados pero breves.

Buena práctica para los registros MCP: registrar como mínimo: timestamp, request_id/traceId, nombre de la herramienta, parámetros (anonimizados), estado (ok/error) y tiempo de ejecución.

Un sencillo logger.ts para GiftGenius puede verse así:

// src/lib/logger.ts
export function logToolEvent(
  phase: "start" | "end" | "error",
  data: Record<string, unknown>
) {
  const ts = new Date().toISOString();
  console.log(JSON.stringify({ ts, phase, ...data }));
}

Y en el manejador de la herramienta:

// src/mcp/tools/searchGifts.ts
import { logToolEvent } from "@/lib/logger";

export async function searchGiftsTool(args: { q: string }) {
  const traceId = crypto.randomUUID();

  logToolEvent("start", { tool: "search_gifts", traceId, args });

  try {
    // ... búsqueda real de regalos ...
    const results = []; // placeholder

    logToolEvent("end", { tool: "search_gifts", traceId, count: results.length });

    return results;
  } catch (err) {
    logToolEvent("error", { tool: "search_gifts", traceId, error: String(err) });
    throw err;
  }
}

Hay dos matices importantes.

Primero, no guardes en los registros emails completos, teléfonos, números de tarjeta, tokens. No solo es feo, también entra en conflicto con las prácticas básicas de seguridad de MCP.

Segundo, traceId es tu mejor amigo. Cuando miras los registros de Next.js y MCP juntos, te permite correlacionar eventos: la solicitud concreta tools/call, el render de React correspondiente y el log de red del widget.

Cómo entender por los registros dónde falló

Tienes la terminal, y en ella van llegando líneas JSON de logToolEvent. Escenario típico:

  • llegó phase: "start" con tool: "search_gifts";
  • no hay phase: "end", pero sí phase: "error" y stack trace;
  • de ahí se ve que la herramienta llegó a tu lógica, pero algo falló dentro — por ejemplo, una llamada a API externa, parseo, trabajo con la BD.

Si no ves registros para ese nombre de herramienta, significa que la solicitud ni siquiera llegó a la herramienta. Entonces subes en el stack: túnel, endpoint /mcp, solicitud JSON tools/call.

4. MCP Inspector: depuración de MCP antes de ChatGPT

Si los registros son tus ojos, MCP Inspector (o MCPJam Inspector) es el microscopio.

Más sobre MCP Inspector y para qué sirve

En el módulo de MCP ya conectamos el Inspector para comprobar el servidor «Hello, MCP». Aquí lo usamos como herramienta principal de depuración: primero comprobamos que MCP vive por sí mismo y solo después vamos a Dev Mode y al UI.

El Inspector es una aplicación aparte (a menudo una interfaz web más CLI) que hace de cliente MCP. Se conecta a tu servidor por HTTP/SSE o por stdin/stdout, ejecuta tools/list, tools/call y muestra los mensajes JSON en crudo, el handshake, la lista de herramientas, recursos, etc.

La idea principal: sacar ChatGPT de la ecuación. Si una herramienta no funciona, primero quieres saber si el servidor está vivo, si el protocolo y el esquema son correctos, antes de culpar a GPT.

Mini‑flujo de trabajo con el Inspector

El escenario típico de depuración local es así:

  1. Ejecutas npm run dev para levantar Next.js + el endpoint MCP.
  2. Ejecutas MCP Inspector, por ejemplo:
npx @modelcontextprotocol/inspector

(el comando concreto depende de la herramienta que uses).

  1. En el Inspector indicas la URL de tu endpoint MCP, por ejemplo http://localhost:3000/api/mcp (o el túnel HTTPS, si quieres comprobarlo también).
  2. Miras si pasó el handshake: el servidor debe responder con las capabilities soportadas, la lista de tools, recursos, etc.
  3. Invocas manualmente la herramienta que te interesa: eliges search_gifts, introduces los argumentos {"q": "para una chica de hasta 30"}, pulsas «Call tool» y miras:
    • si llegó respuesta;
    • si no se devolvió un error JSON‑RPC o MCP;
    • qué escribe el servidor en los registros para esa llamada.

Si en el Inspector ya todo falla, ni siquiera necesitas abrir ChatGPT: arregla el servidor MCP.

Si en el Inspector todo va bien y ChatGPT sigue quejándose, el problema está más arriba: URL de Dev Mode, autorización, comportamiento del modelo.

Ejemplo «rompimos la herramienta a propósito»

Tomemos nuestro search_gifts y lo rompemos a propósito:

export async function searchGiftsTool(args: { q: string }) {
  if (args.q === "falla") {
    throw new Error("Error didáctico para demostrar la depuración");
  }
  // ... lógica normal ...
  return [];
}

Después:

  1. En el Inspector llamas search_gifts con el argumento {"q": "falla"}.
  2. En los registros ves phase: "error" y el stack trace.
  3. Te aseguras de que el servidor MCP devuelve el error honestamente.

Luego, cuando conectes todo esto a ChatGPT Dev Mode y le pidas al modelo «elige un regalo con la palabra "falla"», intentará invocar la herramienta y mostrará al usuario un mensaje del estilo «I encountered an error running the tool». Se ve: el error no aparece por el modelo, sino por tu excepción explícita.

Este truco entrena bien la mente: separas claramente el error de negocio (lanzamos Error nosotros mismos) del de protocolo (rompimos el JSON, nombre de herramienta incorrecto, etc.).

5. Depuración del widget: DevTools, estado y «banner de depuración»

Cuando el servidor MCP está más o menos claro, pasamos al frontend: el widget de Apps SDK.

Dónde y cómo ver los errores del widget

Tu widget se renderiza dentro de ChatGPT en un iframe aislado. Pero la buena noticia: ese iframe tiene las mismas DevTools del navegador.

Mini‑procedimiento:

  1. Abre ChatGPT en el navegador (Chrome/Edge/Firefox).
  2. Abre DevTools (normalmente F12 o Ctrl+Shift+I).
  3. Pestaña Console: elige el contexto del frame donde vive tu widget (a menudo el dominio web-sandbox.oaiusercontent.com).
  4. Recarga el chat/envía un mensaje para que GPT muestre tu App.

Si el widget:

  • no aparece en absoluto;
  • aparece gris/vacío;
  • muestra un error en rojo en la consola

— casi seguro es un problema del código React: propiedad inalcanzable, import incorrecto, hook mal usado, etc.

La pestaña Network también es útil. Ahí verás:

  • la carga del bundle JS de tu aplicación (si hay 404/500, el problema está en el servidor de desarrollo/túnel);
  • las solicitudes que tu widget hace hacia afuera mediante window.fetch, y respuestas 4xx/5xx.

Banner de depuración sencillo

Muy útil: añade al componente raíz del widget un pequeño «banner de depuración» que en Dev Mode muestre qué entorno es y qué versión de build.

Por ejemplo:

// src/components/DebugBanner.tsx
export function DebugBanner() {
  if (process.env.NODE_ENV !== "development") return null;

  return (
    <div style={{ padding: 4, background: "#222", color: "#0f0", fontSize: 10 }}>
      ENV: dev | build: local | {new Date().toLocaleTimeString()}
    </div>
  );
}

Y en el componente raíz del widget:

// src/app/widget/page.tsx
import { DebugBanner } from "@/components/DebugBanner";

export default function GiftGeniusWidget() {
  return (
    <div>
      <DebugBanner />
      {/* resto del UI de búsqueda de regalos */}
    </div>
  );
}

Si abriste ChatGPT, iniciaste la App y no ves el banner, significa que tu JS no llegó al navegador en absoluto: o hay un error de compilación, o un problema con el endpoint, o el widget simplemente no está registrado en el servidor MCP.

Estado local y manejo de errores

Tu widget ya debería poder mostrar distintos estados: carga, éxito, error. Si no, es buen momento para añadirlos.

Mini‑patrón:

const [status, setStatus] = useState<"idle"|"loading"|"error"|"success">("idle");

async function handleSearch(query: string) {
  try {
    setStatus("loading");
    // invocamos la herramienta MCP vía window.openai.callTool o un hook de Apps SDK
    setStatus("success");
  } catch (e) {
    console.error("Search failed", e);
    setStatus("error");
  }
}

En JSX:

{status === "error" && (
  <div style={{ color: "red" }}>Algo salió mal, inténtalo de nuevo.</div>
)}

Para depurar es crítico que:

  • no tragues las excepciones (si no, la consola queda vacía y el UI simplemente «se cuelga»);
  • reflejes explícitamente el error en el UI; de lo contrario, al usuario le parecerá que la App se murió.

6. Dev Mode como parte de la depuración: qué hace y cómo no culparlo injustamente

Ahora incluimos en el cuadro a ChatGPT Dev Mode. Hasta ahora miramos solo tu código. Pero a veces todo funciona localmente, en el Inspector todo está perfecto y, aun así, ChatGPT responde «Error talking to [AppName]» o ni siquiera ofrece tu App.

Qué hace Dev Mode

Dev Mode es el modo de ChatGPT donde puedes:

  • crear y editar tus Apps;
  • indicar el endpoint del servidor MCP (normalmente https://tu-dominio/mcp o /api/mcp);
  • actualizar rápido el manifiesto y metadatos sin publicar en la Store.

Desde el punto de vista de depuración, Dev Mode es solo otra capa de configuración:

  • si allí la URL es incorrecta;
  • si olvidaste /mcp al final;
  • si el túnel te dio un nuevo dominio y no actualizaste los ajustes

— ChatGPT sencillamente no puede llegar a tu servidor.

Escenario típico de rotura en Dev Mode

Un clásico:

  1. Levantaste un túnel https://abcd.ngrok.io, lo indicaste en Dev Mode, todo funcionó.
  2. Al día siguiente reiniciaste ngrok y obtuviste https://efgh.ngrok.io.
  3. En Dev Mode sigue https://abcd.ngrok.io/mcp.
  4. ChatGPT escribe «Error talking to GiftGenius».

MCP Inspector, apuntado a http://localhost:3000/api/mcp, muestra que todo está bien. Eso significa que MCP vive, pero ChatGPT mira al lugar equivocado.

Solución: entra en los ajustes de Dev Mode, actualiza la URL, sin olvidar /mcp al final.

Dev Mode vs Store

En esta lección hablamos solo de Dev Mode: es tu sandbox. Aquí es normal cambiar la URL a menudo, reconectar el túnel, ajustar el esquema de herramientas.

Cuando luego vayas a la Store, el endpoint quedará más fijado y esos trucos ya no serán buena idea. Pero para la Store aún faltan varios módulos, así que por ahora rompe y arregla con tranquilidad en Dev Mode.

7. Mini‑algoritmo de depuración: qué hacer cuando «nada funciona»

Ahora reunimos todo en un algoritmo práctico. En esencia, son los mismos tres niveles de depuración del principio de la lección, pero como pasos secuenciales.

Supón que abriste ChatGPT, elegiste GiftGenius, pediste «Elige un regalo de hasta 30 $ para un amigo geek», y:

  • GPT no dice nada sobre la App;
  • o escribe «Error talking to GiftGenius»;
  • o se abre un widget vacío/gris.

¿Cómo no desesperar?

Paso 1 (nivel MCP/servidor). Comprobar MCP mediante el Inspector y los registros

Primero ignoramos GPT y el UI. Nos interesa solo el servidor.

  1. Asegúrate de que npm run dev está en ejecución y que el endpoint (/api/mcp) responde.
  2. Conecta MCP Inspector a http://localhost:3000/api/mcp o a tu túnel.
  3. Comprueba el handshake: la lista de tools debe mostrarse.
  4. Invoca manualmente la misma herramienta que se supone que debe llamar GPT (por ejemplo, search_gifts) con argumentos similares.

Si ya aquí todo falla, arregla MCP: esquemas, lógica de negocio, llamadas de red. Usa los registros y el traceId para entender qué falla exactamente.

Paso 2 (nivel protocolo/Dev Mode). Comprobar Dev Mode y la URL

Si en el Inspector todo está bien y ChatGPT sigue sin ver tu App o escribe sobre problemas de conexión:

  1. Abre los ajustes de Dev Mode de tu App.
  2. Mira qué URL está indicada para MCP.
  3. Verifícala con lo que realmente escucha tu servidor/túnel (y no olvides comprobar que al final esté /mcp, si tu servidor lo requiere).

A menudo el problema está justo ahí.

Paso 3 (nivel UI). Comprobar el widget con DevTools

Si ChatGPT invoca correctamente las herramientas (se ve en los registros de MCP), pero el widget se comporta raro:

  1. Abre DevTools del navegador en la página de ChatGPT.
  2. Pestaña Console: elige el contexto del iframe de tu widget.
  3. Mira los errores de JS.
  4. Pestaña Network: asegúrate de que:
    • el bundle JS del widget se carga sin 404/500;
    • las solicitudes adicionales (mediante fetch/window.openai.fetch) devuelven respuestas con sentido.

En paralelo, mira tu DebugBanner: si no aparece, significa que ni siquiera llegaste al árbol de React.

Paso 4. Usar Dev Mode para reproducir el informe de error

Cuando recibas un informe de error de un colega/usuario, intenta conservar el prompt exacto donde falló. En Dev Mode puedes reproducir muy rápido el escenario:

  1. Ejecuta npm run dev, levanta el túnel.
  2. En Dev Mode, elige la App.
  3. Pega el prompt problemático.
  4. En paralelo:
    • mira qué solicitudes JSON llegan a MCP en los registros;
    • en el Inspector, si hace falta, repite tools/call con los mismos argumentos.

Así conviertes «a veces algo no funciona» en un escenario reproducible.

8. Pequeños retoques de código para una depuración cómoda

Para afianzar el material, añadamos un par de fragmentos útiles a nuestra aplicación GiftGenius.

Configuración de entorno y niveles de registro

En algún lugar de la configuración del servidor conviene declarar explícitamente el endpoint MCP y el nivel de registro:

// src/config.ts
export const config = {
  mcpEndpoint:
    process.env.NODE_ENV === "development"
      ? "http://localhost:3000/api/mcp" // el túnel cubre esto
      : "https://api.giftgenius.com/api/mcp",
  logLevel: process.env.NODE_ENV === "development" ? "DEBUG" : "ERROR",
};

Y en logToolEvent puedes tener en cuenta logLevel para no hacer spam en producción.

Registro de errores estructurados de MCP

Al procesar herramientas, intenta capturar los errores esperados y devolver mensajes comprensibles, en lugar de tirar todo con throw:

export async function searchGiftsTool(args: { q: string }) {
  const traceId = crypto.randomUUID();
  logToolEvent("start", { tool: "search_gifts", traceId, args });

  try {
    // ... código normal ...
    return { content: [{ type: "text", text: "Se han encontrado 3 regalos" }] };
  } catch (err) {
    logToolEvent("error", { tool: "search_gifts", traceId, error: String(err) });

    return {
      content: [{ type: "text", text: "Error al buscar regalos. Inténtalo más tarde." }],
      isError: true,
    };
  }
}

Así ChatGPT verá que el resultado está marcado como isError, podrá comunicar el problema correctamente al usuario, y tú verás qué ocurrió en los registros.

9. Errores típicos en la depuración local de ChatGPT App

Error n.º 1: depurar «a través de GPT», y no a través del servidor y el inspector.
Es muy tentador mirar solo lo que responde el modelo e intentar adivinar dónde está el bug. Pero el modelo es la capa más alta. Si el servidor MCP no funciona por sí mismo (a mano, con el Inspector), no esperes milagros de GPT. Primero logra un MCP estable y luego conecta ChatGPT.

Error n.º 2: no mirar registros o registrar absolutamente todo.
La ausencia de registros lleva a la ceguera total: no sabes qué herramienta se invocó, con qué argumentos, ni cómo terminó. El exceso de registros, por el contrario, convierte la consola en una «matriz» de líneas inconexas. Es mejor tener un registro compacto y estructurado con tool, args (anonimizados), traceId, status y tiempo de ejecución.

Error n.º 3: guardar datos sensibles en los registros.
Registrar tokens, emails completos y números de tarjeta es mala práctica tanto por seguridad como por la política de OpenAI. En los registros debe haber solo la información que realmente ayuda a depurar; los datos personales se enmascaran o no se escriben.

Error n.º 4: culpar a Dev Mode de todos los males.
Dev Mode suele convertirse en chivo expiatorio: «Seguro que OpenAI rompió algo». En realidad, muy a menudo el problema es que olvidaste actualizar la URL tras reiniciar el túnel o indicaste la ruta equivocada (/ en lugar de /mcp). Antes de escribir al soporte, entra en los ajustes de Dev Mode y verifica que el endpoint coincida con la dirección real del servidor.

Error n.º 5: ignorar DevTools y un error en el widget.
Un widget vacío o gris casi siempre significa un error de JavaScript en el cliente. Si solo miras los registros de MCP, pero no abres DevTools en ChatGPT, estás viendo solo la mitad del cuadro. El hábito de pulsar F12 y mirar Console/Network te ahorrará horas de vida.

Error n.º 6: intentar «arreglar» un bug con retrasos mágicos.
A veces apetece hacer un setTimeout o un retraso al estilo Thread.sleep «para que todo tenga tiempo de cargarse». En el mundo MCP/Next/React eso casi siempre es el remedio equivocado: el problema suele estar en el esquema, el endpoint incorrecto o un error de código, no en que «el servidor no llegó a tiempo». Mejor entender dónde está exactamente la rotura (Inspector → Dev Mode → widget) que enterrarla bajo retrasos.

Error n.º 7: desplegar en Vercel sin asegurarte de que localmente todo funciona.
Las ganas de «ir rápido a producción» se entienden, pero mover un MCP roto a Vercel es la manera perfecta de obtener dos niveles de problemas: local y producción. En este módulo exigimos conscientemente: primero MCP Jam/Inspector → todo ok, Dev Mode → escenarios básicos funcionando, y solo después el despliegue.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION