cc-jandi 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,74 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ import { IncomingWebhookService } from "../../services/IncomingWebhookService.js";
4
+ import { resolveIncomingToken } from "../../utils/resolveToken.js";
5
+ import { validateHexColor } from "../../utils/validateColor.js";
6
+ import { JandiColors } from "../../types/common.js";
7
+ class SendRichMessageTool extends MCPTool {
8
+ name = "send_rich_message";
9
+ description = "Send a rich message with color and attachments to Jandi channel via Incoming Webhook";
10
+ schema = {
11
+ message: {
12
+ type: z.string(),
13
+ description: "The main message text to send to Jandi",
14
+ },
15
+ color: {
16
+ type: z.string().optional(),
17
+ description: "Hex color code for the message attachment (e.g., '#FF0000' for red). Default is Jandi's default color",
18
+ },
19
+ connectInfo: {
20
+ type: z.array(z.object({
21
+ title: z.string().optional(),
22
+ description: z.string().optional(),
23
+ imageUrl: z.string().optional(),
24
+ })).optional(),
25
+ description: "Array of additional information sections with optional title, description, and image URL",
26
+ },
27
+ token: {
28
+ type: z.string().optional(),
29
+ description: "Jandi webhook token (32-character hexadecimal string). If not provided, will use tokenAlias or default token",
30
+ },
31
+ tokenAlias: {
32
+ type: z.string().optional(),
33
+ description: "Token alias from configuration (e.g., 'default', 'dev', 'prod'). If not provided, will use 'default'",
34
+ },
35
+ };
36
+ async execute(input) {
37
+ try {
38
+ const resolved = resolveIncomingToken(input);
39
+ if (!resolved.success) {
40
+ return { success: false, error: resolved.error };
41
+ }
42
+ let normalizedColor = input.color;
43
+ if (input.color) {
44
+ const colorResult = validateHexColor(input.color);
45
+ if (!colorResult.valid) {
46
+ return { success: false, error: colorResult.error };
47
+ }
48
+ normalizedColor = colorResult.normalized;
49
+ }
50
+ const message = IncomingWebhookService.createRichMessage(input.message, normalizedColor, input.connectInfo);
51
+ const result = await IncomingWebhookService.sendMessage(resolved.config, message);
52
+ if (result.success) {
53
+ return {
54
+ success: true,
55
+ data: {
56
+ message: "Rich message sent successfully to Jandi",
57
+ tokenUsed: resolved.config.alias || 'direct',
58
+ messageDetails: {
59
+ color: input.color || JandiColors.DEFAULT,
60
+ attachments: input.connectInfo?.length || 0
61
+ }
62
+ }
63
+ };
64
+ }
65
+ else {
66
+ return { success: false, error: result.error };
67
+ }
68
+ }
69
+ catch (error) {
70
+ return { success: false, error: `Unexpected error: ${error}` };
71
+ }
72
+ }
73
+ }
74
+ export default SendRichMessageTool;
@@ -0,0 +1,95 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ import { IncomingWebhookService } from "../../services/IncomingWebhookService.js";
4
+ import { resolveIncomingToken } from "../../utils/resolveToken.js";
5
+ import { JandiColors, MessageType } from "../../types/common.js";
6
+ class TestWebhookTool extends MCPTool {
7
+ name = "test_webhook";
8
+ description = "Test Jandi Incoming Webhook connection by sending various test messages";
9
+ schema = {
10
+ token: {
11
+ type: z.string().optional(),
12
+ description: "Jandi webhook token to test (32-character hexadecimal string). If not provided, will use tokenAlias or default token",
13
+ },
14
+ tokenAlias: {
15
+ type: z.string().optional(),
16
+ description: "Token alias from configuration to test (e.g., 'default', 'dev', 'prod'). If not provided, will use 'default'",
17
+ },
18
+ testType: {
19
+ type: z.enum(['basic', 'rich', 'all']).optional(),
20
+ description: "Type of test to perform: 'basic' (simple message), 'rich' (rich message), 'all' (comprehensive test). Default is 'basic'",
21
+ },
22
+ };
23
+ async execute(input) {
24
+ try {
25
+ const resolved = resolveIncomingToken(input);
26
+ if (!resolved.success) {
27
+ return { success: false, error: resolved.error };
28
+ }
29
+ const config = resolved.config;
30
+ const testType = input.testType || 'basic';
31
+ const timestamp = new Date().toISOString();
32
+ const results = [];
33
+ if (testType === 'basic' || testType === 'all') {
34
+ const basicMessage = IncomingWebhookService.createBasicMessage(`🧪 Basic webhook test - ${timestamp}`);
35
+ const basicResult = await IncomingWebhookService.sendMessage(config, basicMessage);
36
+ results.push({
37
+ type: 'basic',
38
+ success: basicResult.success,
39
+ error: basicResult.error
40
+ });
41
+ }
42
+ if (testType === 'rich' || testType === 'all') {
43
+ const richMessage = IncomingWebhookService.createRichMessage(`🎨 Rich webhook test - ${timestamp}`, JandiColors.BLUE, [{
44
+ title: 'Test Section',
45
+ description: 'This is a test of rich message functionality with color and attachments.',
46
+ }]);
47
+ const richResult = await IncomingWebhookService.sendMessage(config, richMessage);
48
+ results.push({
49
+ type: 'rich',
50
+ success: richResult.success,
51
+ error: richResult.error
52
+ });
53
+ }
54
+ if (testType === 'all') {
55
+ const statusTypes = [MessageType.SUCCESS, MessageType.WARNING, MessageType.ERROR];
56
+ for (const statusType of statusTypes) {
57
+ const statusMessage = IncomingWebhookService.createStatusMessage(`${statusType.toUpperCase()} status test - ${timestamp}`, statusType, `This is a test of ${statusType} message type`);
58
+ const statusResult = await IncomingWebhookService.sendMessage(config, statusMessage);
59
+ results.push({
60
+ type: `status_${statusType}`,
61
+ success: statusResult.success,
62
+ error: statusResult.error
63
+ });
64
+ }
65
+ }
66
+ const successCount = results.filter(r => r.success).length;
67
+ const totalCount = results.length;
68
+ const allSuccessful = successCount === totalCount;
69
+ return {
70
+ success: allSuccessful,
71
+ data: {
72
+ message: allSuccessful
73
+ ? `All ${totalCount} webhook tests passed successfully`
74
+ : `${successCount}/${totalCount} webhook tests passed`,
75
+ tokenUsed: config.alias || 'direct',
76
+ testType,
77
+ timestamp,
78
+ results,
79
+ summary: {
80
+ total: totalCount,
81
+ successful: successCount,
82
+ failed: totalCount - successCount
83
+ }
84
+ }
85
+ };
86
+ }
87
+ catch (error) {
88
+ return {
89
+ success: false,
90
+ error: `Unexpected error during webhook test: ${error}`
91
+ };
92
+ }
93
+ }
94
+ }
95
+ export default TestWebhookTool;
@@ -0,0 +1,54 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ import { IncomingWebhookService } from "../../services/IncomingWebhookService.js";
4
+ import { resolveIncomingToken } from "../../utils/resolveToken.js";
5
+ class ValidateTokenTool extends MCPTool {
6
+ name = "validate_token";
7
+ description = "Validate a Jandi Incoming Webhook token by sending a test message";
8
+ schema = {
9
+ token: {
10
+ type: z.string().optional(),
11
+ description: "Jandi webhook token to validate (32-character hexadecimal string). If not provided, will use tokenAlias or default token",
12
+ },
13
+ tokenAlias: {
14
+ type: z.string().optional(),
15
+ description: "Token alias from configuration to validate (e.g., 'default', 'dev', 'prod'). If not provided, will use 'default'",
16
+ },
17
+ };
18
+ async execute(input) {
19
+ try {
20
+ const resolved = resolveIncomingToken(input);
21
+ if (!resolved.success) {
22
+ return { success: false, error: resolved.error };
23
+ }
24
+ const result = await IncomingWebhookService.validateToken(resolved.config.token, resolved.config.url);
25
+ if (result.success) {
26
+ return {
27
+ success: true,
28
+ data: {
29
+ message: "Token is valid and webhook is working",
30
+ tokenAlias: resolved.config.alias || 'direct',
31
+ tokenFormat: "valid"
32
+ }
33
+ };
34
+ }
35
+ else {
36
+ return {
37
+ success: false,
38
+ error: result.error,
39
+ data: {
40
+ tokenAlias: resolved.config.alias || 'direct',
41
+ tokenFormat: "valid_format_but_failed"
42
+ }
43
+ };
44
+ }
45
+ }
46
+ catch (error) {
47
+ return {
48
+ success: false,
49
+ error: `Unexpected error during token validation: ${error}`
50
+ };
51
+ }
52
+ }
53
+ }
54
+ export default ValidateTokenTool;
@@ -0,0 +1,217 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ class GenerateOutgoingHandlerTool extends MCPTool {
4
+ name = "generate_outgoing_handler";
5
+ description = "Generate a server handler for Jandi Outgoing Webhook. Creates ready-to-run code for receiving and responding to Jandi outgoing webhook events.";
6
+ schema = {
7
+ framework: {
8
+ type: z.enum(['express', 'fastapi', 'flask']),
9
+ description: "Server framework to generate the handler for: 'express' (Node.js), 'fastapi' (Python), 'flask' (Python)",
10
+ },
11
+ verificationToken: {
12
+ type: z.string().optional(),
13
+ description: "Verification token to validate incoming requests from Jandi. If provided, the handler will verify the token.",
14
+ },
15
+ handlerLogic: {
16
+ type: z.string().optional(),
17
+ description: "Custom handler logic description. Default generates an echo bot that responds with the received message.",
18
+ },
19
+ };
20
+ async execute(input) {
21
+ try {
22
+ let script;
23
+ let fileName;
24
+ let executionInstructions;
25
+ switch (input.framework) {
26
+ case 'express':
27
+ script = this.generateExpressHandler(input);
28
+ fileName = 'jandi-webhook-handler.js';
29
+ executionInstructions = 'npm init -y && npm install express && node jandi-webhook-handler.js';
30
+ break;
31
+ case 'fastapi':
32
+ script = this.generateFastAPIHandler(input);
33
+ fileName = 'jandi_webhook_handler.py';
34
+ executionInstructions = 'pip install fastapi uvicorn && uvicorn jandi_webhook_handler:app --reload --port 3000';
35
+ break;
36
+ case 'flask':
37
+ script = this.generateFlaskHandler(input);
38
+ fileName = 'jandi_webhook_handler.py';
39
+ executionInstructions = 'pip install flask && python jandi_webhook_handler.py';
40
+ break;
41
+ }
42
+ return {
43
+ success: true,
44
+ data: {
45
+ framework: input.framework,
46
+ fileName,
47
+ executionInstructions,
48
+ script,
49
+ message: `Successfully generated ${input.framework} handler for Jandi Outgoing Webhook`,
50
+ notes: [
51
+ "The handler listens on port 3000 by default",
52
+ "Use simulate_outgoing_payload to generate test payloads",
53
+ "Response format: { body, connectColor?, connectInfo? }",
54
+ "Response body max 5000 chars, total response max 256KB"
55
+ ]
56
+ }
57
+ };
58
+ }
59
+ catch (error) {
60
+ return { success: false, error: `Error generating handler: ${error}` };
61
+ }
62
+ }
63
+ generateExpressHandler(input) {
64
+ const tokenCheck = input.verificationToken
65
+ ? `
66
+ // Verify token
67
+ if (req.body.token !== '${input.verificationToken}') {
68
+ return res.status(401).json({ error: 'Invalid token' });
69
+ }
70
+ `
71
+ : '';
72
+ return `const express = require('express');
73
+ const app = express();
74
+ const PORT = 3000;
75
+
76
+ app.use(express.json());
77
+
78
+ // Jandi Outgoing Webhook Handler
79
+ app.post('/webhook', (req, res) => {
80
+ const { token, teamName, roomName, writerName, writerEmail, text, keyword, createdAt } = req.body;
81
+ // Team Outgoing Webhook uses writer object: req.body.writer.name, req.body.writer.email
82
+ ${tokenCheck}
83
+ console.log(\`[\${new Date().toISOString()}] Message from \${writerName || req.body.writer?.name}: \${text}\`);
84
+
85
+ // Respond with a Jandi message format
86
+ // Return empty 200 to acknowledge without sending a response message
87
+ res.json({
88
+ body: \`Received: \${text}\`,
89
+ connectColor: "#FAC11B",
90
+ connectInfo: [{
91
+ title: "Webhook Response",
92
+ description: \`Processed message from \${writerName || req.body.writer?.name} in \${roomName}\`
93
+ }]
94
+ });
95
+ });
96
+
97
+ app.listen(PORT, () => {
98
+ console.log(\`Jandi Outgoing Webhook handler listening on port \${PORT}\`);
99
+ console.log(\`Endpoint: http://localhost:\${PORT}/webhook\`);
100
+ });
101
+ `;
102
+ }
103
+ generateFastAPIHandler(input) {
104
+ const tokenCheck = input.verificationToken
105
+ ? `
106
+ # Verify token
107
+ if payload.token != "${input.verificationToken}":
108
+ raise HTTPException(status_code=401, detail="Invalid token")
109
+ `
110
+ : '';
111
+ return `from fastapi import FastAPI, HTTPException
112
+ from pydantic import BaseModel
113
+ from typing import Optional, Dict, Any, List
114
+ from datetime import datetime
115
+ import uvicorn
116
+
117
+ app = FastAPI(title="Jandi Outgoing Webhook Handler")
118
+
119
+
120
+ class Writer(BaseModel):
121
+ id: Optional[str] = None
122
+ name: str
123
+ email: str
124
+ phoneNumber: Optional[str] = None
125
+
126
+
127
+ class OutgoingPayload(BaseModel):
128
+ token: str
129
+ teamName: str
130
+ roomName: str
131
+ writerName: Optional[str] = None # Standard Outgoing
132
+ writerEmail: Optional[str] = None # Standard Outgoing
133
+ writer: Optional[Writer] = None # Team Outgoing
134
+ text: str
135
+ keyword: str
136
+ createdAt: str
137
+ data: Optional[Dict[str, Any]] = None
138
+ platform: Optional[str] = None
139
+ ip: Optional[str] = None
140
+
141
+
142
+ class ConnectInfo(BaseModel):
143
+ title: Optional[str] = None
144
+ description: Optional[str] = None
145
+ imageUrl: Optional[str] = None
146
+
147
+
148
+ class WebhookResponse(BaseModel):
149
+ body: str
150
+ connectColor: Optional[str] = "#FAC11B"
151
+ connectInfo: Optional[List[ConnectInfo]] = None
152
+
153
+
154
+ @app.post("/webhook", response_model=WebhookResponse)
155
+ async def handle_webhook(payload: OutgoingPayload):
156
+ ${tokenCheck}
157
+ writer_name = payload.writerName or (payload.writer.name if payload.writer else "Unknown")
158
+ print(f"[{datetime.now().isoformat()}] Message from {writer_name}: {payload.text}")
159
+
160
+ return WebhookResponse(
161
+ body=f"Received: {payload.text}",
162
+ connectColor="#FAC11B",
163
+ connectInfo=[ConnectInfo(
164
+ title="Webhook Response",
165
+ description=f"Processed message from {writer_name} in {payload.roomName}"
166
+ )]
167
+ )
168
+
169
+
170
+ if __name__ == "__main__":
171
+ uvicorn.run(app, host="0.0.0.0", port=3000)
172
+ `;
173
+ }
174
+ generateFlaskHandler(input) {
175
+ const tokenCheck = input.verificationToken
176
+ ? `
177
+ # Verify token
178
+ if data.get('token') != '${input.verificationToken}':
179
+ return jsonify({'error': 'Invalid token'}), 401
180
+ `
181
+ : '';
182
+ return `from flask import Flask, request, jsonify
183
+ from datetime import datetime
184
+
185
+ app = Flask(__name__)
186
+
187
+
188
+ @app.route('/webhook', methods=['POST'])
189
+ def handle_webhook():
190
+ data = request.get_json()
191
+ ${tokenCheck}
192
+ # Support both standard and team outgoing webhook formats
193
+ writer_name = data.get('writerName') or data.get('writer', {}).get('name', 'Unknown')
194
+ text = data.get('text', '')
195
+ room_name = data.get('roomName', '')
196
+
197
+ print(f"[{datetime.now().isoformat()}] Message from {writer_name}: {text}")
198
+
199
+ # Respond with Jandi message format
200
+ return jsonify({
201
+ 'body': f'Received: {text}',
202
+ 'connectColor': '#FAC11B',
203
+ 'connectInfo': [{
204
+ 'title': 'Webhook Response',
205
+ 'description': f'Processed message from {writer_name} in {room_name}'
206
+ }]
207
+ })
208
+
209
+
210
+ if __name__ == '__main__':
211
+ print('Jandi Outgoing Webhook handler listening on port 3000')
212
+ print('Endpoint: http://localhost:3000/webhook')
213
+ app.run(host='0.0.0.0', port=3000, debug=True)
214
+ `;
215
+ }
216
+ }
217
+ export default GenerateOutgoingHandlerTool;
@@ -0,0 +1,103 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ class SimulateOutgoingPayloadTool extends MCPTool {
4
+ name = "simulate_outgoing_payload";
5
+ description = "Generate a simulated Jandi Outgoing Webhook payload JSON for testing your webhook handler server. Useful for local development and testing.";
6
+ schema = {
7
+ webhookType: {
8
+ type: z.enum(['outgoing', 'team-outgoing']).optional(),
9
+ description: "Type of outgoing webhook: 'outgoing' (standard) or 'team-outgoing' (team). Default is 'outgoing'",
10
+ },
11
+ text: {
12
+ type: z.string(),
13
+ description: "The message text that triggered the webhook",
14
+ },
15
+ keyword: {
16
+ type: z.string().optional(),
17
+ description: "The keyword that triggered the outgoing webhook. Default is 'test'",
18
+ },
19
+ teamName: {
20
+ type: z.string().optional(),
21
+ description: "Team name for the payload. Default is 'TestTeam'",
22
+ },
23
+ roomName: {
24
+ type: z.string().optional(),
25
+ description: "Chat room name for the payload. Default is 'TestRoom'",
26
+ },
27
+ writerName: {
28
+ type: z.string().optional(),
29
+ description: "Writer name for the payload. Default is 'TestUser'",
30
+ },
31
+ writerEmail: {
32
+ type: z.string().optional(),
33
+ description: "Writer email for the payload. Default is 'test@example.com'",
34
+ },
35
+ };
36
+ async execute(input) {
37
+ try {
38
+ const webhookType = input.webhookType || 'outgoing';
39
+ const now = new Date().toISOString();
40
+ if (webhookType === 'team-outgoing') {
41
+ const payload = {
42
+ token: "abcdef0123456789abcdef0123456789",
43
+ teamName: input.teamName || "TestTeam",
44
+ roomName: input.roomName || "TestRoom",
45
+ writer: {
46
+ id: "12345",
47
+ name: input.writerName || "TestUser",
48
+ email: input.writerEmail || "test@example.com",
49
+ phoneNumber: "+82-10-1234-5678"
50
+ },
51
+ text: input.text,
52
+ keyword: input.keyword || "test",
53
+ createdAt: now,
54
+ data: {},
55
+ platform: "web",
56
+ ip: "127.0.0.1"
57
+ };
58
+ return {
59
+ success: true,
60
+ data: {
61
+ webhookType: 'team-outgoing',
62
+ payload,
63
+ payloadJson: JSON.stringify(payload, null, 2),
64
+ curlCommand: this.generateCurlCommand(payload),
65
+ message: "Team Outgoing Webhook test payload generated. Use curlCommand to test your handler."
66
+ }
67
+ };
68
+ }
69
+ const payload = {
70
+ token: "abcdef0123456789abcdef0123456789",
71
+ teamName: input.teamName || "TestTeam",
72
+ roomName: input.roomName || "TestRoom",
73
+ writerName: input.writerName || "TestUser",
74
+ writerEmail: input.writerEmail || "test@example.com",
75
+ text: input.text,
76
+ keyword: input.keyword || "test",
77
+ createdAt: now,
78
+ data: {},
79
+ platform: "web",
80
+ ip: "127.0.0.1"
81
+ };
82
+ return {
83
+ success: true,
84
+ data: {
85
+ webhookType: 'outgoing',
86
+ payload,
87
+ payloadJson: JSON.stringify(payload, null, 2),
88
+ curlCommand: this.generateCurlCommand(payload),
89
+ message: "Outgoing Webhook test payload generated. Use curlCommand to test your handler."
90
+ }
91
+ };
92
+ }
93
+ catch (error) {
94
+ return { success: false, error: `Error generating payload: ${error}` };
95
+ }
96
+ }
97
+ generateCurlCommand(payload) {
98
+ return `curl -X POST http://localhost:3000/webhook \\
99
+ -H "Content-Type: application/json" \\
100
+ -d '${JSON.stringify(payload)}'`;
101
+ }
102
+ }
103
+ export default SimulateOutgoingPayloadTool;
@@ -0,0 +1,96 @@
1
+ import { MCPTool } from "mcp-framework";
2
+ import { z } from "zod";
3
+ import { validateHexColor } from "../../utils/validateColor.js";
4
+ class ValidateOutgoingResponseTool extends MCPTool {
5
+ name = "validate_outgoing_response";
6
+ description = "Validate a Jandi Outgoing Webhook response format. Checks body length, color format, connectInfo structure, and total size limits.";
7
+ schema = {
8
+ body: {
9
+ type: z.string(),
10
+ description: "The response body text to validate",
11
+ },
12
+ connectColor: {
13
+ type: z.string().optional(),
14
+ description: "Hex color code to validate (e.g., '#FF0000')",
15
+ },
16
+ connectInfo: {
17
+ type: z.array(z.object({
18
+ title: z.string().optional(),
19
+ description: z.string().optional(),
20
+ imageUrl: z.string().optional(),
21
+ })).optional(),
22
+ description: "Array of connectInfo sections to validate",
23
+ },
24
+ };
25
+ async execute(input) {
26
+ const MAX_BODY_LENGTH = 5000;
27
+ const MAX_DATA_SIZE = 256 * 1024;
28
+ const issues = [];
29
+ const warnings = [];
30
+ // Validate body
31
+ if (!input.body || input.body.trim().length === 0) {
32
+ issues.push("body is required and cannot be empty");
33
+ }
34
+ else if (input.body.length > MAX_BODY_LENGTH) {
35
+ issues.push(`body exceeds maximum length of ${MAX_BODY_LENGTH} characters (current: ${input.body.length})`);
36
+ }
37
+ // Validate color format
38
+ if (input.connectColor) {
39
+ const colorResult = validateHexColor(input.connectColor);
40
+ if (!colorResult.valid) {
41
+ issues.push(`connectColor: ${colorResult.error}`);
42
+ }
43
+ }
44
+ // Validate connectInfo
45
+ if (input.connectInfo) {
46
+ if (!Array.isArray(input.connectInfo)) {
47
+ issues.push("connectInfo must be an array");
48
+ }
49
+ else {
50
+ input.connectInfo.forEach((info, index) => {
51
+ if (!info.title && !info.description && !info.imageUrl) {
52
+ warnings.push(`connectInfo[${index}] has no title, description, or imageUrl - it will be empty`);
53
+ }
54
+ if (info.imageUrl && !info.imageUrl.match(/^https?:\/\/.+/)) {
55
+ issues.push(`connectInfo[${index}].imageUrl must be a valid HTTP/HTTPS URL`);
56
+ }
57
+ });
58
+ }
59
+ }
60
+ // Validate total data size
61
+ const response = {
62
+ body: input.body,
63
+ connectColor: input.connectColor,
64
+ connectInfo: input.connectInfo
65
+ };
66
+ const dataSize = JSON.stringify(response).length;
67
+ if (dataSize > MAX_DATA_SIZE) {
68
+ issues.push(`Total response data exceeds maximum size of ${MAX_DATA_SIZE} bytes (current: ${dataSize})`);
69
+ }
70
+ const isValid = issues.length === 0;
71
+ return {
72
+ success: true,
73
+ data: {
74
+ valid: isValid,
75
+ message: isValid
76
+ ? "Outgoing webhook response format is valid"
77
+ : "Outgoing webhook response has validation issues",
78
+ issues: issues.length > 0 ? issues : undefined,
79
+ warnings: warnings.length > 0 ? warnings : undefined,
80
+ stats: {
81
+ bodyLength: input.body?.length || 0,
82
+ maxBodyLength: MAX_BODY_LENGTH,
83
+ totalDataSize: dataSize,
84
+ maxDataSize: MAX_DATA_SIZE,
85
+ connectInfoSections: input.connectInfo?.length || 0
86
+ },
87
+ validResponseFormat: {
88
+ body: "string (required, max 5000 chars)",
89
+ connectColor: "string (optional, hex color e.g. '#FAC11B')",
90
+ connectInfo: "array (optional, [{title?, description?, imageUrl?}])"
91
+ }
92
+ }
93
+ };
94
+ }
95
+ }
96
+ export default ValidateOutgoingResponseTool;