Skip to content

Flex Message Schema Validation Crashes on Optional Fields #314

@danielk0k1

Description

@danielk0k1

System Information

  • OS: Ubuntu 22.04
  • Node.js Version: Node 22.13.0
  • line-bot-mcp-server version: 0.0.1 (latest)
  • AI Agent: Manus AI

Expected Behavior

When sending a flex message with optional fields (header, body, footer) omitted, the schema validation should:

  1. Accept the omission of optional fields as valid input
  2. Successfully validate the provided fields
  3. Send the message to LINE Messaging API
  4. Return a success response with message ID and quote token

According to the LINE Flex Message specification, the header, body, and footer fields in a bubble container are optional.

Current Behavior

When sending a flex message with any of the optional fields (header, body, or footer) omitted, the schema validation crashes with:

Error: Cannot read properties of undefined (reading 'type')

This error occurs during Zod schema validation before the message reaches the LINE API, making it impossible to send flex messages that omit any of these optional fields.

Impact: This bug makes the flex message feature essentially unusable for typical use cases, as most flex message designs do not require all three sections (header, body, footer).

Steps to Reproduce

Test Case 1: Flex Message Without Header and Footer (FAILS)

mcp-cli tool call push_flex_message --server line --input '{
  "message": {
    "type": "flex",
    "altText": "Test Message",
    "contents": {
      "type": "bubble",
      "body": {
        "type": "box",
        "layout": "vertical",
        "contents": [
          {"type": "text", "text": "Hello World"}
        ]
      }
    }
  }
}'

Result: ❌ Error: Cannot read properties of undefined (reading 'type')

Test Case 2: Flex Message With All Three Fields (SUCCEEDS)

mcp-cli tool call push_flex_message --server line --input '{
  "message": {
    "type": "flex",
    "altText": "Complete Test",
    "contents": {
      "type": "bubble",
      "header": {
        "type": "box",
        "layout": "vertical",
        "contents": [{"type": "text", "text": "Header"}]
      },
      "body": {
        "type": "box",
        "layout": "vertical",
        "contents": [{"type": "text", "text": "Body"}]
      },
      "footer": {
        "type": "box",
        "layout": "vertical",
        "contents": [{"type": "text", "text": "Footer"}]
      }
    }
  }
}'

Result: ✅ Success

Comparison

Configuration Header Body Footer Result
Test Case 1 ❌ Omitted ✅ Present ❌ Omitted ❌ FAILS
Test Case 2 ✅ Present ✅ Present ✅ Present ✅ SUCCEEDS

Root Cause Analysis

File: src/common/schema/flexMessage.ts
Lines: 344, 349, 353

The bug is in the Zod schema refinement validators:

export const flexBubbleSchema = z.object({
  type: z.literal("bubble"),
  // ... other fields ...
  header: flexComponentSchema
    .optional()
    .describe("Header must be a Box")
    .refine(component => component.type === "box", "Header must be a Box"),  // ❌ Line 344
  hero: flexComponentSchema.optional(),
  body: flexComponentSchema
    .optional()
    .describe("Body must be a Box")
    .refine(component => component.type === "box", "Body must be a Box"),    // ❌ Line 349
  footer: flexComponentSchema
    .optional()
    .describe("Footer must be a Box")
    .refine(component => component.type === "box", "Footer must be a Box"),  // ❌ Line 353
  // ... other fields ...
});

The Problem:

When header, body, or footer are omitted (undefined), the .refine() callback still executes and attempts to access component.type on an undefined value, causing a JavaScript error.

Zod's .optional() makes the field optional for input validation, but .refine() still runs on the value even when it's undefined. The refinement function doesn't check if the component exists before accessing its properties.

Proposed Fix

Add a null/undefined check in the .refine() callbacks:

export const flexBubbleSchema = z.object({
  type: z.literal("bubble"),
  // ... other fields ...
  header: flexComponentSchema
    .optional()
    .describe("Header must be a Box")
    .refine(component => !component || component.type === "box", "Header must be a Box"),  // ✅ Fixed
  hero: flexComponentSchema.optional(),
  body: flexComponentSchema
    .optional()
    .describe("Body must be a Box")
    .refine(component => !component || component.type === "box", "Body must be a Box"),    // ✅ Fixed
  footer: flexComponentSchema
    .optional()
    .describe("Footer must be a Box")
    .refine(component => !component || component.type === "box", "Footer must be a Box"),  // ✅ Fixed
  // ... other fields ...
});

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions