n8n-io/n8n · error · Error

Tool "${this.name}" requires an input schema

Error message

Tool "${this.name}" requires an input schema

What it means

Tool.build() requires an input schema because the runtime needs it to validate the LLM's tool-call arguments, generate the model-facing JSON schema, and type the handler's first argument. Set it via .input(zodSchema) or .input(jsonSchema). Without it, the agent loop cannot serialize the tool for the model.

Source

Thrown at packages/@n8n/agents/src/sdk/tool.ts:364

		this.providerOptionsValue = { ...this.providerOptionsValue, ...options };
		return this;
	}

	/**
	 * Validate configuration and produce a `BuiltTool`.
	 *
	 * @throws if name, description, input schema, or handler is missing.
	 * @throws if suspend is declared without resume or vice versa.
	 */
	build(): BuiltTool {
		if (!this.name) {
			throw new Error('Tool name is required');
		}
		if (!this.desc) {
			throw new Error(`Tool "${this.name}" requires a description`);
		}
		if (!this.inputSchema) {
			throw new Error(`Tool "${this.name}" requires an input schema`);
		}
		if (!this.handlerFn) {
			throw new Error(`Tool "${this.name}" requires a handler`);
		}

		const hasSuspend = this.suspendSchemaValue !== undefined;
		const hasResume = this.resumeSchemaValue !== undefined;

		if (hasSuspend && !hasResume) {
			throw new Error(`Tool "${this.name}" has .suspend() but missing .resume()`);
		}
		if (hasResume && !hasSuspend) {
			throw new Error(`Tool "${this.name}" has .resume() but missing .suspend()`);
		}

		const hasApproval =
			(this.requireApprovalValue ?? false) || this.needsApprovalFnValue !== undefined;
		if (hasApproval && (hasSuspend || hasResume)) {

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Add .input(z.object({...})) with a Zod schema describing the tool's arguments.
  2. For tools that take no meaningful input, pass z.object({}).describe('no arguments') rather than skipping .input().
  3. If loading schemas dynamically, assert the schema is defined before calling .input().

Example fix

// before
const t = new Tool('ping').description('Health check').handler(async () => 'ok');
agent.tool(t); // throws: requires an input schema

// after
const t = new Tool('ping')
  .description('Health check')
  .input(z.object({}).describe('no arguments'))
  .handler(async () => 'ok');
Defensive patterns

Strategy: type-guard

Validate before calling

import { isZodSchema } from '@n8n/agents/utils/zod'; // or local util

function attachInput(tool: Tool, schema: unknown) {
  if (!isZodSchema(schema) && !(schema && typeof schema === 'object' && 'type' in schema)) {
    throw new Error('Input schema must be a Zod schema or JSON Schema object');
  }
  return tool.input(schema as any);
}

Type guard

function isInputSchema(value: unknown): value is import('zod').ZodType | import('json-schema').JSONSchema7 {
  if (!value) return false;
  // Zod check
  if (typeof value === 'object' && '_def' in value && typeof (value as any)._def === 'object') return true;
  // JSON Schema check
  if (typeof value === 'object' && 'type' in value) return true;
  return false;
}

Prevention

When it happens

Trigger: Calling new Tool('name').description('...').handler(...) and omitting .input(...), then building. Also triggered when .input() is called with undefined due to a variable that wasn't set.

Common situations: Forgetting .input() in a quick prototype; passing an undefined schema variable; assuming the handler will accept raw args without schema validation.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/eadcf646ca9ba9b1. Report an issue: GitHub.