MCP does not report a tool failure as a JSON-RPC error. It reports it as a successful result carrying isError: true, with the reason as text. The node only checked `error`, so it read the failure as data and went on to JSON.parse the sentence "HTTP call failed with status 404" as a product. The throw is what made the webhook answer nothing at all. Now checks result.isError, distinguishes a missing product from any other tool failure, and guards every parse of a payload it did not construct. Through the facade, escalate: true becomes a task in state failed rather than a completed one. Also corrects this file: the earlier note said the run completed in success with every node executing. It did not — that was the wrong execution. n8n's executions list did not return them newest-first as assumed, and the conclusion drawn from it sent the search to the wrong place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Workflows
Exported as JSON and committed. The n8n instance is not the source of truth.
Deploying a change
n8n does not re-register an active workflow when you PATCH it. The running webhook keeps executing the definition it was activated with, so an edit appears to have no effect — and the failure it keeps producing is the old one, which is a very good way to spend an afternoon debugging something already fixed.
Deactivate, apply, reactivate, in that order:
POST /rest/workflows/<id>/deactivate {"versionId": "<current>"}
PATCH /rest/workflows/<id> {"nodes":…, "connections":…, "versionId": "<current>"}
POST /rest/workflows/<id>/activate {"versionId": "<current>"}
versionId changes at every step, so re-read it each time. And note
PATCH {"active": true} returns 200 and leaves the workflow inactive, silently:
activation has its own endpoint.
approvisionnement-check-availability
Working. POST /webhook/check-availability with the end user's token in
X-Subject-Token:
"Peut-on promettre S10_1949 ?"
→ {"answer":"Yes — 1952 Alpine Renault 1300 (S10_1949) can be promised:
7305 in stock, above the reorder threshold of 50.","escalate":false}
"Peut-on promettre la Vespa ?"
→ {"answer":"No product reference in the question…","escalate":true}
It exchanges the token itself, using the agent's client secret from the pod
environment so the committed JSON holds no secret, then talks MCP in two calls:
initialize to obtain the session id from a response header, then
tools/call. No notifications/initialized is needed.
MCP rather than the REST route, which would have been one call instead of two: the per-tool ACL by consumer group lives on the MCP route only. Going through REST would give an autonomous agent broader access than a conversational one, which is exactly backwards.
The product code is read with a regex, not a model. This agent's critical path is rules; a question naming no product escalates rather than being guessed at. The regex is loose after the underscore on purpose — a reference that looks valid but does not exist must reach the catalog and come back as "no such product", because answering "you named no product" would be false.
The one that took longest
A product code that does not exist returned an empty body, and the reason is worth keeping.
MCP does not report a tool failure as a JSON-RPC error. It reports it as a
perfectly successful result carrying isError: true, with the reason as
text:
data: {"jsonrpc":"2.0","result":{"content":[{"type":"text",
"text":"HTTP call failed with status 404: {\"detail\":\"Not found.\"}"}],
"isError":true},"id":2}
A client that only checks rpc.error therefore reads a failure as data. This
one went on to JSON.parse the sentence "HTTP call failed with status 404" as
if it were a product, threw, and the webhook answered nothing at all.
Two lessons, both now in the node:
- check
result.isError, not justerror— they mean different things: a protocol failure versus a tool that ran and failed; - guard every
JSON.parseof a payload you did not construct. The throw is what turned a wrong answer into no answer, which for an autonomous agent is the worse of the two.
Now:
"Peut-on promettre S99_NOPE ?"
→ {"answer":"There is no product S99_NOPE in the catalog.","escalate":true}
and through the A2A facade, escalate: true becomes a task in state failed
rather than a completed one — the honest shape for "a human has to look at
this".