Skip to main content
Glama
validators.ts7.51 kB
/** * Input Validation Utilities * * Provides validation functions for ByteBot API inputs using Zod schemas */ import { z } from 'zod'; import { TaskPriority, TaskStatus, MouseButton, ScrollDirection, } from '../types/bytebot.js'; /** * Validation schemas */ // Task-related schemas export const TaskPrioritySchema = z.enum(['LOW', 'MEDIUM', 'HIGH', 'URGENT']); export const TaskStatusSchema = z.enum([ 'PENDING', 'IN_PROGRESS', 'NEEDS_HELP', 'NEEDS_REVIEW', 'COMPLETED', 'CANCELLED', 'FAILED', ]); export const CreateTaskSchema = z.object({ description: z.string().min(1, 'Description cannot be empty'), priority: TaskPrioritySchema.optional(), files: z .array( z.object({ name: z.string(), content: z.string(), // Base64 mimeType: z.string().optional(), }) ) .optional(), }); export const UpdateTaskSchema = z.object({ status: TaskStatusSchema.optional(), priority: TaskPrioritySchema.optional(), message: z.string().optional(), }); export const ListTasksSchema = z.object({ status: TaskStatusSchema.optional(), priority: TaskPrioritySchema.optional(), limit: z.number().int().positive().optional(), offset: z.number().int().nonnegative().optional(), }); // Desktop action schemas export const MouseButtonSchema = z.enum(['left', 'right', 'middle']); export const ScrollDirectionSchema = z.enum(['up', 'down', 'left', 'right']); export const CoordinatesSchema = z.object({ x: z.number().int().nonnegative(), y: z.number().int().nonnegative(), }); export const MoveMouseSchema = z.object({ action: z.literal('move_mouse'), x: z.number().int().nonnegative(), y: z.number().int().nonnegative(), }); export const ClickMouseSchema = z.object({ action: z.literal('click_mouse'), x: z.number().int().nonnegative(), y: z.number().int().nonnegative(), button: MouseButtonSchema.optional(), count: z.number().int().positive().optional(), }); export const DragMouseSchema = z.object({ action: z.literal('drag_mouse'), from_x: z.number().int().nonnegative(), from_y: z.number().int().nonnegative(), to_x: z.number().int().nonnegative(), to_y: z.number().int().nonnegative(), }); export const ScrollSchema = z.object({ action: z.literal('scroll'), direction: ScrollDirectionSchema, count: z.number().int().positive().optional(), }); export const TypeTextSchema = z.object({ action: z.literal('type_text'), text: z.string().min(1), delay: z.number().int().nonnegative().optional(), }); export const PasteTextSchema = z.object({ action: z.literal('paste_text'), text: z.string().min(1), }); export const PressKeysSchema = z.object({ action: z.literal('press_keys'), keys: z.array(z.string()).min(1), }); export const ReadFileSchema = z.object({ action: z.literal('read_file'), path: z.string().min(1), }); export const WriteFileSchema = z.object({ action: z.literal('write_file'), path: z.string().min(1), content: z.string(), // Base64 }); export const ApplicationSchema = z.object({ action: z.literal('application'), name: z.string().min(1), }); export const WaitSchema = z.object({ action: z.literal('wait'), duration: z.number().int().positive(), }); export const ScreenshotSchema = z.object({ action: z.literal('screenshot'), }); export const CursorPositionSchema = z.object({ action: z.literal('cursor_position'), }); /** * Validation helper functions */ export function validateTaskId(taskId: string): void { if (!taskId || taskId.trim().length === 0) { throw new Error('Task ID cannot be empty'); } } export function validateTaskDescription(description: string): void { if (!description || description.trim().length === 0) { throw new Error('Task description cannot be empty'); } if (description.length > 10000) { throw new Error('Task description is too long (max 10000 characters)'); } } export function validatePriority(priority: string): TaskPriority { const result = TaskPrioritySchema.safeParse(priority); if (!result.success) { throw new Error( `Invalid priority: ${priority}. Must be one of: LOW, MEDIUM, HIGH, URGENT` ); } return result.data; } export function validateStatus(status: string): TaskStatus { const result = TaskStatusSchema.safeParse(status); if (!result.success) { throw new Error( `Invalid status: ${status}. Must be one of: PENDING, IN_PROGRESS, NEEDS_HELP, NEEDS_REVIEW, COMPLETED, CANCELLED, FAILED` ); } return result.data; } export function validateCoordinates(x: number, y: number): void { if (!Number.isInteger(x) || x < 0) { throw new Error(`Invalid x coordinate: ${x}. Must be a non-negative integer`); } if (!Number.isInteger(y) || y < 0) { throw new Error(`Invalid y coordinate: ${y}. Must be a non-negative integer`); } } export function validateMouseButton(button: string): MouseButton { const result = MouseButtonSchema.safeParse(button); if (!result.success) { throw new Error( `Invalid mouse button: ${button}. Must be one of: left, right, middle` ); } return result.data; } export function validateScrollDirection(direction: string): ScrollDirection { const result = ScrollDirectionSchema.safeParse(direction); if (!result.success) { throw new Error( `Invalid scroll direction: ${direction}. Must be one of: up, down, left, right` ); } return result.data; } export function validateFilePath(path: string): void { if (!path || path.trim().length === 0) { throw new Error('File path cannot be empty'); } } export function validateFileSize(base64Content: string, maxSize: number): void { // Calculate approximate size from base64 // Base64 is ~1.37x larger than binary, so divide by 1.37 to get approximate binary size const approximateSize = (base64Content.length * 3) / 4; if (approximateSize > maxSize) { const sizeMB = (approximateSize / (1024 * 1024)).toFixed(2); const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(2); throw new Error( `File size (${sizeMB}MB) exceeds maximum allowed size (${maxSizeMB}MB)` ); } } export function validateBase64(content: string): void { // Basic base64 validation const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/; if (!base64Regex.test(content)) { throw new Error('Invalid base64 content'); } } export function validateTimeout(timeout: number): void { if (!Number.isInteger(timeout) || timeout <= 0) { throw new Error(`Invalid timeout: ${timeout}. Must be a positive integer`); } if (timeout > 600000) { // 10 minutes throw new Error('Timeout cannot exceed 10 minutes (600000ms)'); } } export function validatePollInterval(interval: number): void { if (!Number.isInteger(interval) || interval < 100) { throw new Error( `Invalid poll interval: ${interval}. Must be at least 100ms` ); } if (interval > 60000) { // 1 minute throw new Error('Poll interval cannot exceed 1 minute (60000ms)'); } } /** * Sanitize user input to prevent injection attacks */ export function sanitizeInput(input: string): string { // Remove null bytes and control characters except newlines and tabs return input.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, ''); } /** * Validate and parse JSON safely */ export function safeJsonParse<T>(json: string): T { try { return JSON.parse(json) as T; } catch (error) { throw new Error(`Invalid JSON: ${error instanceof Error ? error.message : 'Unknown error'}`); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sensuslab/spark-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server