yikart/AiToEarn · error · McpError

InternalError

InternalError

Error message

Tool result does not match outputSchema: ${validation.error.message}

What it means

After a tool executes, formatToolResult validates the raw result against the tool's declared Zod outputSchema. If the tool's return value does not conform, the handler throws McpError InternalError, because returning structuredContent that violates the advertised schema would break the MCP contract.

Source

Thrown at project/aitoearn-backend/libs/nest-mcp/src/services/handlers/mcp-tools.handler.ts:46

  private buildDefaultContentBlock(result: any) {
    return [
      {
        type: 'text',
        text: JSON.stringify(result),
      },
    ]
  }

  private formatToolResult(result: any, outputSchema?: ZodTypeAny): any {
    if (result && typeof result === 'object' && Array.isArray(result.content)) {
      return result
    }

    if (outputSchema) {
      const validation = outputSchema.safeParse(result)
      if (!validation.success) {
        throw new McpError(
          ErrorCode.InternalError,
          `Tool result does not match outputSchema: ${validation.error.message}`,
        )
      }
      return {
        structuredContent: result,
        content: this.buildDefaultContentBlock(result),
      }
    }

    return {
      content: this.buildDefaultContentBlock(result),
    }
  }

  registerHandlers(mcpServer: McpServer, httpRequest: HttpRequest) {
    if (this.registry.getTools(this.mcpModuleId).length === 0) {
      this.logger.debug('No tools registered, skipping tool handlers')

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Align the tool's return value with its declared outputSchema (map/normalize the result before returning)
  2. Run the schema in a test to see the exact validation.error details
  3. Relax or update the outputSchema if the new shape is the intended contract
  4. Handle failure paths explicitly so the tool never returns null/undefined for a schema-required field

Example fix

// before
async run(args) { return (await api.getData()).data; }
// after
async run(args) {
  const { data } = await api.getData();
  return outputSchema.parse({ items: data.items ?? [], total: data.total ?? 0 });
}
Defensive patterns

Strategy: validation

Validate before calling

const parsed = outputSchema.safeParse(result);
if (!parsed.success) {
  throw new Error(`Tool result invalid: ${parsed.error.message}`);
}
return parsed.data;

Type guard

function matchesOutputSchema(result: unknown, schema: z.ZodTypeAny): result is z.infer<typeof schema> {
  return schema.safeParse(result).success;
}

Try / catch

try {
  const res = await client.callTool({ name, arguments });
  return res;
} catch (e) {
  if (e.code === ErrorCode.InternalError && /outputSchema/.test(e.message)) {
    console.error('Server tool returned schema-violating result:', e.message);
  }
  throw e;
}

Prevention

When it happens

Trigger: A tool implementation returns data that fails outputSchema.safeParse — missing required fields, wrong types, extra/null fields where the schema forbids them, or unparsed JSON strings returned instead of objects.

Common situations: Schema tightened after the tool code was written; tool returns null/undefined on an internal failure path; remote API change altered the response shape; developer returned the raw SDK response instead of the mapped object.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/eaae1ba44608f63d. Report an issue: GitHub.