-
Notifications
You must be signed in to change notification settings - Fork 84
Description
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:
- Accept the omission of optional fields as valid input
- Successfully validate the provided fields
- Send the message to LINE Messaging API
- 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 ...
});