skills/n8n-code-tool/SKILL.md
--- name: n8n-code-tool description: Write JavaScript or Python for the n8n Custom Code Tool (@n8n/n8n-nodes-langchain.toolCode) — the AI-agent-callable tool, NOT the workflow Code node. Use when building a Code Tool attached to an AI Agent, writing code that an LLM will invoke, parsing the `query` input, returning a string result, defining an input schema for structured arguments (specifyInputSchema, jsonSchemaExample, DynamicStructuredTool), or troubleshooting errors like "Wrong output type re
npx skillsauth add czlonkowski/n8n-skills skills/n8n-code-toolInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Expert guidance for writing code inside @n8n/n8n-nodes-langchain.toolCode — the tool an AI Agent can invoke, not the regular workflow Code node.
The Custom Code Tool looks like a Code node in the editor — same JavaScript editor, similar layout — but it is a completely different node from a different package with a different runtime contract.
| | Code node | Custom Code Tool |
|---|---|---|
| Node type | n8n-nodes-base.code | @n8n/n8n-nodes-langchain.toolCode |
| Package | n8n-nodes-base | @n8n/n8n-nodes-langchain |
| Invoked by | Previous node (workflow flow) | AI Agent (LangChain) |
| Input | $input.all() — item stream | query — string or object from LLM |
| Return | [{json: {...}}] (items array) | A string |
| $fromAI() | N/A | Not available (see Errors) |
| $helpers | Full helpers incl. httpRequest | Not exposed to the tool sandbox |
| State | Per-run execution data | No getContext, no $getWorkflowStaticData |
If you treat it like a Code node, it fails. The rest of this skill covers the Code Tool's actual contract.
// `query` is whatever the AI sent (a string by default)
return `You asked: ${query}`;
# `_query` is whatever the AI sent (a string by default)
return f"You asked: {_query}"
"The response property should be a string, but it is an object".query (JS), _query (Python). You cannot rename it.$fromAI() inside the Code Tool sandbox — it throws "No execution data available".[{json: {...}}] return format — that's for Code nodes. Throws "Wrong output type returned".The Code Tool has two input shapes, controlled by specifyInputSchema:
specifyInputSchema: false)The AI passes a single string as query. If you need multiple fields, the AI has to stuff them into that one string and you parse them out. In practice, LLMs will happily pass a JSON string if your description tells them to.
// Parse a JSON string the AI sent
let params;
try {
params = typeof query === 'string' ? JSON.parse(query) : query;
} catch (e) {
throw new Error('Expected a JSON object. Parser said: ' + e.message);
}
const price = Number(params.price);
const months = Number(params.months);
// ...
return JSON.stringify({ monthly_payment: /* ... */ });
Pros: simplest to set up, one field to describe. Cons: no schema validation — if the LLM forgets a field, the tool throws at runtime.
Best for: quick prototypes, tools with one natural input (a question, a URL, a text blob).
specifyInputSchema: true)The tool becomes a LangChain DynamicStructuredTool. The LLM sees a typed argument schema and passes a validated object as query. You access fields directly.
// query is now an object matching your schema
const price = query.price;
const months = query.months;
const residual_percent = query.residual_percent;
const monthly = computeAnnuity(price, months, residual_percent);
return JSON.stringify({ monthly_payment: monthly });
Schema is defined via either:
schemaType: "fromJson" + jsonSchemaExample (n8n v≥1.3) — paste an example JSON, n8n infers the schemaschemaType: "manual" + inputSchema — write a full JSON Schema yourselfPros: LLM gets type hints, invalid calls rejected before your code runs, cleaner code. Cons: a little more setup; requires n8n version with schema support.
Best for: production tools with multiple typed parameters (calculators, API wrappers, anything with numeric fields the LLM tends to stringify).
See: INPUT_SCHEMA.md for complete schema setup.
The return value must be a string. The LLM reads it as the tool's observation.
// ✅ String
return "42";
// ✅ Number (auto-converted to string by n8n)
return 42;
// ✅ JSON-encoded structured result (recommended for rich output)
return JSON.stringify({ result: 42, currency: "SEK" });
// ❌ Raw object → "The response property should be a string, but it is an object"
return { result: 42 };
// ❌ Workflow item format → "Wrong output type returned"
return [{ json: { result: 42 } }];
// ❌ Array → "The response property should be a string, but it is an object"
return [1, 2, 3];
When your tool has more than a trivial scalar output, return a JSON string:
return JSON.stringify({
monthly_payment_sek: 5405,
loan_amount: 351920,
total_cost_of_credit: 63295
});
The LLM parses JSON reliably and can pick the fields it needs to present to the user.
Errors don't just stop the workflow — they go back to the LLM, which usually corrects its call and retries. Use that:
// Option A: throw — n8n surfaces the message to the agent
if (!isFinite(price)) throw new Error('price must be a number, e.g. 439900');
// Option B: return an error string — agent reads it like any tool result
if (!isFinite(price)) return JSON.stringify({ error: 'price must be a number, e.g. 439900' });
Either way, write error messages for the LLM: state what was wrong and what a valid call looks like. A bare throw new Error('invalid input') wastes the retry; an instructive message usually fixes the next call.
These fields are NOT documentation — they are the tool contract the LLM sees. Treat them as prompt engineering.
[A-Za-z0-9_]+ (v1.1+). No spaces, no hyphens, no emoji.calculate_car_loan, get_weather, search_orders.Code Tool (the default) is useless — the agent won't know when to call it.Unstructured example (JSON-in-string pattern):
Deterministiskt beräknar månadskostnad för billån. Anropa med EN JSON-sträng:
{"price":439900,"down_payment":87980,"interest_rate":6.95,"months":36,"residual_percent":50}
Fält: price (SEK), down_payment (SEK), interest_rate (% per år), months, residual_percent (0-99).
Structured example (schema-defined):
Deterministically computes the monthly car-loan payment given price, down payment,
annual interest rate, term, and residual percent. Use whenever the user asks for
monthly cost, total credit cost, or loan breakdown.
"There was an error: 'Cannot assign to read only property \"name\" of object: Error: No execution data available'"Cause: you called $fromAI() inside the Code Tool sandbox.
Fix: $fromAI() is a helper for other tool-enabled nodes (HTTP Request Tool, SendGrid Tool, toolWorkflow, etc.) — it's not exposed inside toolCode. Read the AI's input from query directly (or use specifyInputSchema for structured fields).
"Wrong output type returned"Cause: you returned a workflow-style array like [{ json: { ... } }]. That's the Code node contract, not the Code Tool contract.
Fix: return a string. For structured data, return JSON.stringify(output).
"The response property should be a string, but it is an object"Cause: you returned a plain object or array.
Fix: JSON.stringify() the result, or coerce to a string.
Cause: tool name is generic (Code Tool, My Tool) or description doesn't clearly state when to use it.
Fix: rename to a verb-y name (calculate_car_loan), and rewrite the description to explicitly state the trigger conditions (e.g. "Use this whenever the user asks about monthly cost").
queryCause: unstructured tool with a vague description. The LLM guesses at the format.
Fix: either (a) include a concrete JSON example in the description, or (b) switch to specifyInputSchema: true so the LLM gets a typed schema.
See: ERROR_PATTERNS.md for full catalog with reproductions.
The Code Tool sandbox is narrower than the Code node sandbox. Don't assume helpers carry over:
| Helper | Code node | Code Tool |
|---|---|---|
| $input.all(), $input.first(), $input.item | ✅ | ❌ |
| $node["NodeName"] | ✅ | ❌ |
| $json, $binary | ✅ | ❌ |
| $fromAI() | ❌ | ❌ (despite sitting next to an AI agent) |
| $helpers.httpRequest() | ✅ | ❌ |
| DateTime (Luxon) | ✅ | ✅ (standard in JS sandbox) |
| $jmespath() | ✅ | ❌ |
| this.getContext(...) | ✅ | ❌ |
| $getWorkflowStaticData(...) | ✅ | ❌ |
Implication: the Code Tool is for pure computation. If you need an HTTP call, an API lookup, or cross-invocation state, use a different tool node:
toolWorkflow (Call Sub-workflow Tool) for multi-step logic with access to the full Code node sandboxUse Code Tool when:
Use toolWorkflow (Call Sub-workflow Tool) when:
$fromAI() typing$helpers, credentials, or other nodesUse HTTP Request Tool when:
$fromAI() bindings in URL/query/bodyRule of thumb: if you find yourself wanting $fromAI(), you probably want toolWorkflow instead of toolCode.
A production calculator tool (unstructured, JSON-in-string pattern):
{
"parameters": {
"name": "calculate_car_loan",
"description": "Computes monthly car-loan payment using an annuity formula with residual/balloon. Call with a single JSON string. Example: {\"price\":439900,\"down_payment\":87980,\"interest_rate\":6.95,\"months\":36,\"residual_percent\":50,\"setup_fee\":695,\"monthly_admin_fee\":59}. Required: price, down_payment, interest_rate, months, residual_percent. Optional: setup_fee, monthly_admin_fee (default 0).",
"language": "javaScript",
"jsCode": "let params;\ntry {\n params = typeof query === 'string' ? JSON.parse(query) : query;\n} catch (e) {\n throw new Error('Invalid JSON: ' + e.message);\n}\n\nconst price = Number(params.price);\nconst down_payment = Number(params.down_payment);\nconst interest_rate = Number(params.interest_rate);\nconst months = Number(params.months);\nconst residual_percent= Number(params.residual_percent);\nconst setup_fee = Number(params.setup_fee ?? 0) || 0;\nconst monthly_admin_fee = Number(params.monthly_admin_fee ?? 0) || 0;\n\nif (!isFinite(price) || price <= 0) throw new Error('price must be > 0');\nif (down_payment < 0 || down_payment >= price) throw new Error('down_payment must be in [0, price)');\n\nconst principal = price - down_payment;\nconst residual = price * (residual_percent / 100);\nconst r = interest_rate / 100 / 12;\nconst growth = Math.pow(1 + r, months);\nconst base = r === 0\n ? (principal - residual) / months\n : (principal - residual / growth) * r / (1 - 1 / growth);\nconst monthly_payment = base + monthly_admin_fee;\n\nreturn JSON.stringify({\n monthly_payment_sek: Math.round(monthly_payment),\n loan_amount: Math.round(principal),\n residual_value_sek: Math.round(residual),\n total_cost_of_credit: Math.round(monthly_payment * months + residual + setup_fee - principal)\n});"
},
"type": "@n8n/n8n-nodes-langchain.toolCode",
"typeVersion": 1.3,
"name": "calculate_car_loan"
}
Wire it into an AI Agent via the ai_tool connection type.
n8n-code-javascript: the Code node skill. Most JavaScript patterns (arrays, map/filter, DateTime) transfer — but I/O contract is different. Don't copy data-access code.
n8n-node-configuration: specifyInputSchema is a classic displayOptions-driven conditional field. Use get_node({detail: "standard"}) on @n8n/n8n-nodes-langchain.toolCode to see schema-related properties.
n8n-workflow-patterns: Code Tool sits inside the "AI Agent with tools" pattern. An agent typically has several tools; Code Tool is the "local compute" option.
n8n-validation-expert: the three Code Tool errors listed above have clear signatures — if validation surfaces "Wrong output type returned", you know to switch from array-of-items to a string.
Before deploying a Code Tool:
@n8n/n8n-nodes-langchain.toolCode (not nodes-base.code)calculate_car_loan)query (JS) or _query (Python)$fromAI() in the code body$input / $json / $helpers — those aren't in the sandboxJSON.stringify() for structured output)ai_tool connectionRemember: the Code Tool is a LangChain tool wearing a Code-node UI. Contract is: string in, string out. Everything else follows from that.
tools
Proven workflow architectural patterns from real n8n workflows. Use when building new workflows, designing workflow structure, choosing workflow patterns, planning workflow architecture, or asking about webhook processing, HTTP API integration, database operations, AI agent workflows, batch processing, or scheduled tasks. Always consult this skill when the user asks to create, build, or design an n8n workflow, automate a process, or connect services — even if they don't explicitly mention 'patterns'. Covers webhook, API, database, AI, batch processing, and scheduled automation architectures.
testing
Operation-aware node configuration guidance. Use when configuring nodes, understanding property dependencies, determining required fields, choosing between get_node detail levels, or learning common configuration patterns by node type. Always use this skill when setting up node parameters — it explains which fields are required for each operation, how displayOptions control field visibility, and when to use patchNodeField for surgical edits vs full node updates.
tools
Expert guide for using n8n-mcp MCP tools effectively. Use when searching for nodes, validating configurations, accessing templates, managing workflows, managing credentials, auditing instance security, or using any n8n-mcp tool. Provides tool selection guidance, parameter formats, and common patterns. IMPORTANT — Always consult this skill before calling any n8n-mcp tool — it prevents common mistakes like wrong nodeType formats, incorrect parameter structures, and inefficient tool usage. If the user mentions n8n, workflows, nodes, or automation and you have n8n MCP tools available, use this skill first.
tools
Write Python code in n8n Code nodes. Use when writing Python in n8n, using _input/_json/_node syntax, working with standard library, or need to understand Python limitations in n8n Code nodes. Use this skill when the user specifically requests Python for an n8n Code node. Note — JavaScript is recommended for 95% of use cases — only use Python when the user explicitly prefers it or the task requires Python-specific standard library capabilities (regex, hashlib, statistics). EXCEPTION — for Python in the AI-agent-callable Custom Code Tool (@n8n/n8n-nodes-langchain.toolCode), use the n8n-code-tool skill instead (input is _query, return must be a string).