Hi everyone,
I’m building an n8n workflow where an AI Agent must call a custom JavaScript tool (AvailabilitySlotsParserTool).
This tool receives as input a stringified JSON with real availability data coming from SevenRooms API.
The problem:
Even though the availability JSON clearly contains times, the tool always returns:
{
“ok”: false,
“message”: “
No hay disponibilidad…”,
“slots”:
}
So the AI always believes there is no availability, even when the data obviously contains multiple time slots like “19:00”, “19:30”, etc.
Setup
1. The Tool is configured as a JavaScript Tool (ToolsAgent V2)
-
The tool receives one parameter: query
-
query is always a string containing the JSON object
-
Specify Input Schema is turned off (so the agent sends only { query: “…” })
2. Input that the tool receives
This is the real value of $json.query when the tool runs (stringified JSON):
“{“availability_http”:{“status”:200,“data”:{“availability”:[{“name”:“Dinner”,“shift_persistent_id”:“ahN…”,“times”:[{“sort_order”:52,“time”:“19:00”, … }, {“time”:“19:30”}, {“time”:“20:00”} ]}]},“params”:{“date”:“2025-11-28”,“party_size”:2}}”
We can clearly confirm that:
-
“time”: “19:00”
-
“time”: “19:30”
-
“time”: “20:00”
are present in the JSON.
// AvailabilitySlotsParserTool – versión simple para STRING query
// --------------------------------------------------------------
const raw = $json.query;
// 1) Parsear string a objeto
let q;
try {
q = JSON.parse(raw);
} catch (e) {
return JSON.stringify({
ok: false,
message: “ERROR en AvailabilitySlotsParserTool: query no es JSON válido”,
meta: {},
slots:
});
}
// 2) Extraer availability
const avRoot = q.availability_http || {};
const data = avRoot.data || avRoot.body?.data || {};
let availability = data.availability || ;
if (!Array.isArray(availability) && availability && typeof availability === “object”) {
availability = [availability];
}
// 3) Recoger horarios
const slots = ;
const seen = new Set();
for (const shift of availability) {
if (!shift) continue;
let times = shift.times || ;
if (!Array.isArray(times) && times && typeof times === “object”) {
times = [times];
}
for (const t of times) {
if (!t || !t.time) continue;
const timeStr = String(t.time);
if (!seen.has(timeStr)) {
seen.add(timeStr);
slots.push({ time: timeStr });
}
}
}
// 4) Params / meta
const params = q.params || {};
const dateText = params.date || null;
const paxText = params.party_size || null;
// 5) Mensaje
let message;
if (slots.length > 0) {
slots.sort((a, b) => a.time.localeCompare(b.time));
const lines = slots
.slice(0, 8)
.map((s, i) => ${i + 1}) ${s.time});
message =
✅ Disponibilidad encontrada +
(dateText ? para ${dateText} : “”) +
(paxText ? (${paxText} pax) : “”) +
:\n +
lines.join(“\n”) +
\n\nResponde con el número de opción para continuar.;
} else {
message =
❌ No hay disponibilidad +
(dateText ? para ${dateText} : “”) +
(paxText ? (${paxText} pax) : “”) +
. ¿Quieres que te proponga otros horarios o días cercanos?;
}
// 6) Salida
const result = {
ok: slots.length > 0,
message,
meta: {
venue_id: null,
date: dateText,
party_size: paxText
},
slots
};
return JSON.stringify(result);
Expected vs Actual Behavior
Expected:
The tool should return:
{
“ok”: true,
“slots”: [
{ “time”: “19:00” },
{ “time”: “19:30” },
{ “time”: “20:00” }
],
“message”: “Disponibilidad encontrada…”
}
Actual:
Regardless of input, the tool returns:
{
“ok”: false,
“message”: “
No hay disponibilidad…”,
“slots”:
}
Even though the stringified JSON definitely contains “time”: “19:00” (and others).
What I Suspect
One of these must be happening:
-
The agent is double-escaping the JSON (e.g., JSON → string → string → tool), so parsing becomes inconsistent.
-
ToolsAgent V2 might be wrapping the input inside internal envelope fields in a way that changes $json.query.
-
The tool may be receiving escaped JSON inside a nested query, e.g.:
{
“query”: “{ “query”: “{ … }” }”
}
- Or n8n is passing an object instead of a pure string, causing the string search to fail.
My Question
How do I ensure that a ToolsAgent custom JS tool receives a CLEAN string as $json.query, without extra escaping or wrapping?
Is there a recommended way to debug the exact raw payload that ToolsAgent sends into the tool?
Is there any known issue when tools receive long stringified JSON bodies from previous nodes?
Any insights would be hugely appreciated — been fighting this for days.
Thanks in advance!
