{"record":{"id":"eadcf646ca9ba9b1","repo":"n8n-io/n8n","slug":"tool-this-name-requires-an-input-schema","errorCode":null,"errorMessage":"Tool \"${this.name}\" requires an input schema","messagePattern":"Tool \"(.+?)\" requires an input schema","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/agents/src/sdk/tool.ts","lineNumber":364,"sourceCode":"\t\tthis.providerOptionsValue = { ...this.providerOptionsValue, ...options };\n\t\treturn this;\n\t}\n\n\t/**\n\t * Validate configuration and produce a `BuiltTool`.\n\t *\n\t * @throws if name, description, input schema, or handler is missing.\n\t * @throws if suspend is declared without resume or vice versa.\n\t */\n\tbuild(): BuiltTool {\n\t\tif (!this.name) {\n\t\t\tthrow new Error('Tool name is required');\n\t\t}\n\t\tif (!this.desc) {\n\t\t\tthrow new Error(`Tool \"${this.name}\" requires a description`);\n\t\t}\n\t\tif (!this.inputSchema) {\n\t\t\tthrow new Error(`Tool \"${this.name}\" requires an input schema`);\n\t\t}\n\t\tif (!this.handlerFn) {\n\t\t\tthrow new Error(`Tool \"${this.name}\" requires a handler`);\n\t\t}\n\n\t\tconst hasSuspend = this.suspendSchemaValue !== undefined;\n\t\tconst hasResume = this.resumeSchemaValue !== undefined;\n\n\t\tif (hasSuspend && !hasResume) {\n\t\t\tthrow new Error(`Tool \"${this.name}\" has .suspend() but missing .resume()`);\n\t\t}\n\t\tif (hasResume && !hasSuspend) {\n\t\t\tthrow new Error(`Tool \"${this.name}\" has .resume() but missing .suspend()`);\n\t\t}\n\n\t\tconst hasApproval =\n\t\t\t(this.requireApprovalValue ?? false) || this.needsApprovalFnValue !== undefined;\n\t\tif (hasApproval && (hasSuspend || hasResume)) {","sourceCodeStart":346,"sourceCodeEnd":382,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/agents/src/sdk/tool.ts#L346-L382","documentation":"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.","triggerScenarios":"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.","commonSituations":"Forgetting .input() in a quick prototype; passing an undefined schema variable; assuming the handler will accept raw args without schema validation.","solutions":["Add .input(z.object({...})) with a Zod schema describing the tool's arguments.","For tools that take no meaningful input, pass z.object({}).describe('no arguments') rather than skipping .input().","If loading schemas dynamically, assert the schema is defined before calling .input()."],"exampleFix":"// before\nconst t = new Tool('ping').description('Health check').handler(async () => 'ok');\nagent.tool(t); // throws: requires an input schema\n\n// after\nconst t = new Tool('ping')\n  .description('Health check')\n  .input(z.object({}).describe('no arguments'))\n  .handler(async () => 'ok');","handlingStrategy":"type-guard","validationCode":"import { isZodSchema } from '@n8n/agents/utils/zod'; // or local util\n\nfunction attachInput(tool: Tool, schema: unknown) {\n  if (!isZodSchema(schema) && !(schema && typeof schema === 'object' && 'type' in schema)) {\n    throw new Error('Input schema must be a Zod schema or JSON Schema object');\n  }\n  return tool.input(schema as any);\n}","typeGuard":"function isInputSchema(value: unknown): value is import('zod').ZodType | import('json-schema').JSONSchema7 {\n  if (!value) return false;\n  // Zod check\n  if (typeof value === 'object' && '_def' in value && typeof (value as any)._def === 'object') return true;\n  // JSON Schema check\n  if (typeof value === 'object' && 'type' in value) return true;\n  return false;\n}","tryCatchPattern":null,"preventionTips":["Always pair a handler with its input schema in the same builder block so they aren't separated.","For no-arg tools, pass z.object({}) explicitly rather than skipping .input().","When loading schemas from external sources, assert they parse as Zod or JSON Schema before attaching."],"tags":["tool","builder","validation","zod","schema"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}