n8n-io/n8n · error · Error

Tool "${this.name}" requires a description

Error message

Tool "${this.name}" requires a description

What it means

Tool.build() requires a description because the LLM uses it to decide when and how to invoke the tool. Without a description, the model cannot reason about the tool's purpose, making it effectively invisible to the agent loop. The description must be set via .description() before build.

Source

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

	 * Example: `.providerOptions({ anthropic: { eagerInputStreaming: true } })`
	 */
	providerOptions(options: Record<string, JSONObject>): this {
		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()`);
		}

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Add .description('...') to the builder chain describing what the tool does and when to use it.
  2. Write descriptions aimed at the LLM consumer: state inputs, purpose, and notable side effects.
  3. For tools generated from metadata, assert a description field exists in the source metadata before constructing the Tool.

Example fix

// before
const t = new Tool('get_weather').input(z.object({...})).handler(...);
agent.tool(t); // throws: requires a description

// after
const t = new Tool('get_weather')
  .description('Fetch current weather for a city')
  .input(z.object({ city: z.string() }))
  .handler(...);
Defensive patterns

Strategy: validation

Validate before calling

function toolWithDescription(name: string, desc: string | undefined) {
  if (!desc || desc.trim().length === 0) {
    throw new Error(`Tool ${name} requires a non-empty description`);
  }
  return new Tool(name).description(desc);
}

Type guard

function hasDescription(desc: unknown): desc is string {
  return typeof desc === 'string' && desc.trim().length > 0;
}

Prevention

When it happens

Trigger: Calling new Tool('name') without chaining .description(...), then building (directly or via agent.tool(...)). The check fires only after the name check passes, so the error message includes the tool's name.

Common situations: Forgetting the .description() call in a builder chain; assuming the name doubles as the description; stripping a builder chain down during a refactor and dropping the line.

Related errors


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