Skip to main content
Glama

Google Calendar and Meet MCP Server

by INSIDE-HAIR
meetSchemas.ts19.1 kB
/** * Zod validation schemas for Google Meet MCP Server tools * Focuses on the main Meet API v2 and Calendar tools with clear error messages */ import { z } from 'zod'; /** * Common validation patterns */ const isoDateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2})?$/; const spaceNameRegex = /^spaces\/[a-zA-Z0-9_-]{1,128}$/; const conferenceRecordRegex = /^conferenceRecords\/[a-zA-Z0-9_-]+$/; /** * Meet API v2 Schemas - Top 4 most used tools */ // 1. meet_v2_create_space - Most complex and commonly used export const CreateSpaceSchema = z.object({ access_type: z.enum(['OPEN', 'TRUSTED', 'RESTRICTED']) .default('TRUSTED') .describe('Access control: OPEN (anyone with link), TRUSTED (Google account required), RESTRICTED (invitation only)'), enable_recording: z.boolean() .default(false) .describe('⚠️ Prepares recording capability - must be manually activated during the meeting'), enable_transcription: z.boolean() .default(false) .describe('Enables automatic transcription generation post-meeting'), enable_smart_notes: z.boolean() .default(false) .describe('🧠 Enables AI-powered meeting summaries (requires Gemini license)'), attendance_report: z.boolean() .default(false) .describe('Generates detailed attendance tracking report'), moderation_mode: z.enum(['ON', 'OFF']) .default('OFF') .describe('Host moderation controls for chat and presentation permissions'), chat_restriction: z.enum(['HOSTS_ONLY', 'NO_RESTRICTION']) .optional() .describe('Chat permissions: HOSTS_ONLY restricts chat to hosts only'), present_restriction: z.enum(['HOSTS_ONLY', 'NO_RESTRICTION']) .optional() .describe('Presentation permissions: HOSTS_ONLY restricts screen sharing to hosts'), default_join_as_viewer: z.boolean() .default(false) .describe('Forces participants to join as viewers (cannot unmute/present by default)') }).refine( (data) => { // Business logic validation if (data.enable_recording && data.access_type === 'OPEN') { throw new Error('🔒 Recording cannot be enabled for OPEN access meetings due to privacy and compliance concerns'); } if (data.enable_smart_notes && !data.enable_transcription) { throw new Error('🧠 Smart notes require transcription to be enabled'); } return true; }, 'Meet space configuration validation failed' ); // 2. meet_v2_get_space - Simple but frequently used export const GetSpaceSchema = z.object({ space_name: z.string() .regex(spaceNameRegex, 'Space name must be in format: spaces/{space_id} or spaces/{meeting_code}') .describe('The space identifier (e.g., "spaces/abc-defg-hij" or "spaces/meeting-code")') }); // 3. meet_v2_list_conference_records - Important for historical data export const ListConferenceRecordsSchema = z.object({ filter: z.string() .optional() .describe('Filter expression (e.g., space.name="spaces/{space_id}" or startTime>"2024-01-01T00:00:00Z")'), page_size: z.number() .min(1, 'Page size must be at least 1') .max(50, 'Page size cannot exceed 50 (Google API limit)') .default(10) .describe('Maximum number of conference records to return') }); // 4. meet_v2_get_participant - Key for participant tracking export const GetParticipantSchema = z.object({ participant_name: z.string() .regex(/^conferenceRecords\/[a-zA-Z0-9_-]+\/participants\/[a-zA-Z0-9_-]+$/, 'Participant name must be in format: conferenceRecords/{record_id}/participants/{participant_id}') .describe('Full participant resource name') }); /** * Calendar API v3 Schemas - Top 2 most used tools */ // 1. calendar_v3_create_event - Most commonly used calendar tool export const CreateEventSchema = z.object({ summary: z.string() .min(1, 'Meeting title is required') .max(255, 'Meeting title must be less than 255 characters') .describe('The title/subject of the meeting'), start_time: z.string() .regex(isoDateTimeRegex, 'Start time must be in ISO 8601 format (e.g., "2024-02-01T10:00:00Z" or "2024-02-01T10:00:00-05:00")') .describe('Meeting start time in ISO format with timezone'), end_time: z.string() .regex(isoDateTimeRegex, 'End time must be in ISO 8601 format (e.g., "2024-02-01T11:00:00Z" or "2024-02-01T11:00:00-05:00")') .describe('Meeting end time in ISO format with timezone'), description: z.string() .max(8192, 'Description cannot exceed 8192 characters') .optional() .describe('Optional meeting description or agenda'), location: z.string() .max(1024, 'Location cannot exceed 1024 characters') .optional(), time_zone: z.string() .default('UTC') .describe('Timezone for the event (e.g., "America/New_York", "Europe/London")'), attendees: z.array(z.string().email('Invalid email address')) .optional() .describe('List of attendee email addresses'), create_meet_conference: z.boolean() .default(false) .describe('Automatically creates a Google Meet link for this event'), guest_can_invite_others: z.boolean() .default(true) .describe('Allow attendees to invite additional people'), guest_can_modify: z.boolean() .default(false) .describe('Allow attendees to modify the event details'), guest_can_see_other_guests: z.boolean() .default(true) .describe('Allow attendees to see the full guest list'), calendar_id: z.string() .default('primary') .describe('Calendar ID to create the event in (default: "primary")') }).refine( (data) => { // Time validation const startTime = new Date(data.start_time); const endTime = new Date(data.end_time); if (endTime <= startTime) { throw new Error('⏰ End time must be after start time'); } const diffHours = (endTime.getTime() - startTime.getTime()) / (1000 * 60 * 60); if (diffHours > 24) { throw new Error('📅 Meeting duration cannot exceed 24 hours'); } return true; }, 'Event time validation failed' ); // 2. calendar_v3_list_events - Frequently used for viewing meetings export const ListEventsSchema = z.object({ max_results: z.number() .min(1, 'Maximum results must be at least 1') .max(2500, 'Maximum results cannot exceed 2500 (Google API limit)') .default(10) .describe('Maximum number of events to return'), time_min: z.string() .regex(isoDateTimeRegex, 'Time min must be in ISO 8601 format') .optional() .describe('Lower bound (inclusive) for event start times. Defaults to current time.'), time_max: z.string() .regex(isoDateTimeRegex, 'Time max must be in ISO 8601 format') .optional() .describe('Upper bound (exclusive) for event start times'), calendar_id: z.string() .default('primary') .describe('Calendar ID to list events from (default: "primary")') }).refine( (data) => { if (data.time_min && data.time_max) { const minTime = new Date(data.time_min); const maxTime = new Date(data.time_max); if (maxTime <= minTime) { throw new Error('⏰ time_max must be after time_min'); } } return true; }, 'Time range validation failed' ); // ========== CALENDAR API v3 - HERRAMIENTAS RESTANTES ========== // calendar_v3_list_calendars - Sin parámetros requeridos export const ListCalendarsSchema = z.object({}).describe('List all calendars - no parameters required'); // calendar_v3_get_event - Requiere event_id export const GetEventSchema = z.object({ event_id: z.string() .min(1, 'Event ID is required') .describe('Calendar event ID to retrieve') }).describe('Get specific calendar event details'); // calendar_v3_update_event - Requiere event_id, otros opcionales export const UpdateEventSchema = z.object({ event_id: z.string() .min(1, 'Event ID is required'), summary: z.string() .min(1, 'Event title cannot be empty') .optional(), description: z.string().optional(), location: z.string().optional(), start_time: z.string() .regex(isoDateTimeRegex, 'Start time must be in ISO format (e.g., "2024-02-01T10:00:00Z")') .optional(), end_time: z.string() .regex(isoDateTimeRegex, 'End time must be in ISO format (e.g., "2024-02-01T11:00:00Z")') .optional(), time_zone: z.string().default('UTC'), attendees: z.array(z.string().email('Invalid email address')).optional(), guest_can_invite_others: z.boolean().optional(), guest_can_modify: z.boolean().optional(), guest_can_see_other_guests: z.boolean().optional() }).refine((data) => { if (data.start_time && data.end_time) { const startTime = new Date(data.start_time); const endTime = new Date(data.end_time); if (endTime <= startTime) { throw new Error('⏰ End time must be after start time'); } } return true; }, 'Time validation failed').describe('Update existing calendar event'); // calendar_v3_delete_event - Requiere event_id export const DeleteEventSchema = z.object({ event_id: z.string() .min(1, 'Event ID is required') .describe('Calendar event ID to delete') }).describe('Delete calendar event'); // calendar_v3_move_event - Requiere event_id, source_calendar_id, destination_calendar_id export const MoveEventSchema = z.object({ event_id: z.string() .min(1, 'Event ID is required') .describe('Calendar event ID to move'), source_calendar_id: z.string() .min(1, 'Source calendar ID is required') .describe('ID of the calendar where the event currently exists'), destination_calendar_id: z.string() .min(1, 'Destination calendar ID is required') .describe('ID of the calendar to move the event to') }).describe('Move calendar event from one calendar to another'); // ========== MEET API v2 - HERRAMIENTAS RESTANTES ========== // meet_v2_update_space - Ya existe, pero necesitamos asegurar que esté incluido export const UpdateSpaceSchema = z.object({ space_name: z.string() .regex(spaceNameRegex, 'Space name must be in format: spaces/{space-id} or spaces/{meeting-code}') .describe('Name of the space (spaces/{space_id})'), access_type: z.enum(['OPEN', 'TRUSTED', 'RESTRICTED']).optional(), moderation_mode: z.enum(['ON', 'OFF']).optional(), chat_restriction: z.enum(['HOSTS_ONLY', 'NO_RESTRICTION']).optional(), present_restriction: z.enum(['HOSTS_ONLY', 'NO_RESTRICTION']).optional() }).describe('Update Meet space configuration'); // meet_v2_end_active_conference - Requiere space_name export const EndActiveConferenceSchema = z.object({ space_name: z.string() .regex(spaceNameRegex, 'Space name must be in format: spaces/{space-id} or spaces/{meeting-code}') .describe('Name of the space (spaces/{space_id})') }).describe('End active conference in Meet space'); // meet_v2_get_conference_record - Requiere conference_record_name export const GetConferenceRecordSchema = z.object({ conference_record_name: z.string() .regex(conferenceRecordRegex, 'Conference record name must be in format: conferenceRecords/{record_id}') .describe('Name of the conference record (conferenceRecords/{record_id})') }).describe('Get specific conference record details'); // meet_v2_list_recordings - Requiere conference_record_name export const ListRecordingsSchema = z.object({ conference_record_name: z.string() .regex(conferenceRecordRegex, 'Conference record name must be in format: conferenceRecords/{record_id}') .describe('Name of the conference record (conferenceRecords/{record_id})') }).describe('List recordings for a conference record'); // meet_v2_get_recording - Requiere recording_name const recordingNameRegex = /^conferenceRecords\/[a-zA-Z0-9_-]+\/recordings\/[a-zA-Z0-9_-]+$/; export const GetRecordingSchema = z.object({ recording_name: z.string() .regex(recordingNameRegex, 'Recording name must be in format: conferenceRecords/{record_id}/recordings/{recording_id}') .describe('Name of the recording (conferenceRecords/{record_id}/recordings/{recording_id})') }).describe('Get specific recording details'); // meet_v2_list_transcripts - Requiere conference_record_name export const ListTranscriptsSchema = z.object({ conference_record_name: z.string() .regex(conferenceRecordRegex, 'Conference record name must be in format: conferenceRecords/{record_id}') .describe('Name of the conference record (conferenceRecords/{record_id})') }).describe('List transcripts for a conference record'); // meet_v2_get_transcript - Requiere transcript_name const transcriptNameRegex = /^conferenceRecords\/[a-zA-Z0-9_-]+\/transcripts\/[a-zA-Z0-9_-]+$/; export const GetTranscriptSchema = z.object({ transcript_name: z.string() .regex(transcriptNameRegex, 'Transcript name must be in format: conferenceRecords/{record_id}/transcripts/{transcript_id}') .describe('Name of the transcript (conferenceRecords/{record_id}/transcripts/{transcript_id})') }).describe('Get specific transcript details'); // meet_v2_list_transcript_entries - Requiere transcript_name, page_size opcional export const ListTranscriptEntriesSchema = z.object({ transcript_name: z.string() .regex(transcriptNameRegex, 'Transcript name must be in format: conferenceRecords/{record_id}/transcripts/{transcript_id}') .describe('Name of the transcript (conferenceRecords/{record_id}/transcripts/{transcript_id})'), page_size: z.number() .min(1, 'Page size must be at least 1') .max(1000, 'Page size cannot exceed 1000') .default(100) .describe('Maximum number of entries to return (default: 100, max: 1000)') }).describe('List transcript entries (individual speech segments)'); // meet_v2_list_participants - Requiere conference_record_name, page_size opcional export const ListParticipantsSchema = z.object({ conference_record_name: z.string() .regex(conferenceRecordRegex, 'Conference record name must be in format: conferenceRecords/{record_id}') .describe('Name of the conference record (conferenceRecords/{record_id})'), page_size: z.number() .min(1, 'Page size must be at least 1') .max(100, 'Page size cannot exceed 100') .default(10) .describe('Maximum number of participants to return (default: 10, max: 100)') }).describe('List participants for a conference record'); // meet_v2_get_participant_session - Requiere participant_session_name const participantSessionNameRegex = /^conferenceRecords\/[a-zA-Z0-9_-]+\/participants\/[a-zA-Z0-9_-]+\/participantSessions\/[a-zA-Z0-9_-]+$/; export const GetParticipantSessionSchema = z.object({ participant_session_name: z.string() .regex(participantSessionNameRegex, 'Participant session name must be in format: conferenceRecords/{record_id}/participants/{participant_id}/participantSessions/{session_id}') .describe('Name of the participant session (conferenceRecords/{record_id}/participants/{participant_id}/participantSessions/{session_id})') }).describe('Get specific participant session details'); // meet_v2_list_participant_sessions - Requiere participant_name, page_size opcional const participantNameRegex = /^conferenceRecords\/[a-zA-Z0-9_-]+\/participants\/[a-zA-Z0-9_-]+$/; export const ListParticipantSessionsSchema = z.object({ participant_name: z.string() .regex(participantNameRegex, 'Participant name must be in format: conferenceRecords/{record_id}/participants/{participant_id}') .describe('Name of the participant (conferenceRecords/{record_id}/participants/{participant_id})'), page_size: z.number() .min(1, 'Page size must be at least 1') .max(100, 'Page size cannot exceed 100') .default(10) .describe('Maximum number of sessions to return (default: 10, max: 100)') }).describe('List sessions for a specific participant'); /** * TODAS LAS 17 HERRAMIENTAS - Schemas de validación completos */ // ========== ADDITIONAL CALENDAR API v3 SCHEMAS ========== export const FreeBusyQuerySchema = z.object({ calendar_ids: z.array(z.string().min(1, 'Calendar ID cannot be empty')) .min(1, 'At least one calendar ID is required') .max(50, 'Maximum 50 calendar IDs allowed'), time_min: z.string() .regex(isoDateTimeRegex, 'Start time must be in ISO 8601 format (e.g., "2024-02-01T10:00:00Z")'), time_max: z.string() .regex(isoDateTimeRegex, 'End time must be in ISO 8601 format (e.g., "2024-02-01T23:59:59Z")') }); export const QuickAddSchema = z.object({ calendar_id: z.string().optional() .describe('Calendar ID to add event to (defaults to "primary")'), text: z.string() .min(1, 'Event text cannot be empty') .max(1000, 'Event text cannot exceed 1000 characters') .describe('Natural language description (e.g., "Lunch with John tomorrow at 2pm")') }); export const ValidationSchemas = { // Calendar API v3 Tools (6 herramientas) 'calendar_v3_list_calendars': ListCalendarsSchema, 'calendar_v3_list_events': ListEventsSchema, 'calendar_v3_get_event': GetEventSchema, 'calendar_v3_create_event': CreateEventSchema, 'calendar_v3_update_event': UpdateEventSchema, 'calendar_v3_delete_event': DeleteEventSchema, 'calendar_v3_move_event': MoveEventSchema, // Meet API v2 Tools (11 herramientas) 'meet_v2_create_space': CreateSpaceSchema, 'meet_v2_get_space': GetSpaceSchema, 'meet_v2_update_space': UpdateSpaceSchema, 'meet_v2_end_active_conference': EndActiveConferenceSchema, 'meet_v2_list_conference_records': ListConferenceRecordsSchema, 'meet_v2_get_conference_record': GetConferenceRecordSchema, 'meet_v2_list_recordings': ListRecordingsSchema, 'meet_v2_get_recording': GetRecordingSchema, 'meet_v2_list_transcripts': ListTranscriptsSchema, 'meet_v2_get_transcript': GetTranscriptSchema, 'meet_v2_list_transcript_entries': ListTranscriptEntriesSchema, 'meet_v2_get_participant': GetParticipantSchema, 'meet_v2_list_participants': ListParticipantsSchema, 'meet_v2_get_participant_session': GetParticipantSessionSchema, 'meet_v2_list_participant_sessions': ListParticipantSessionsSchema, // Additional Calendar API v3 Tools (2 herramientas) 'calendar_v3_freebusy_query': FreeBusyQuerySchema, 'calendar_v3_quick_add': QuickAddSchema, }; /** * Helper function to validate tool arguments * @param {string} toolName - Name of the MCP tool * @param {any} args - Arguments to validate * @returns {any} Validated and parsed arguments * @throws {Error} Validation error with helpful message */ export function validateToolArgs(toolName: string, args: any): any { const schema = ValidationSchemas[toolName as keyof typeof ValidationSchemas]; if (!schema) { // If no schema exists, return args as-is (backwards compatibility) return args; } try { return schema.parse(args); } catch (error) { if (error instanceof z.ZodError) { const issues = error.issues.map(issue => `• ${issue.path.join('.')}: ${issue.message}` ).join('\n'); throw new Error(`❌ Validation failed for ${toolName}:\n${issues}\n\n💡 Tip: Check parameter format and required fields`); } throw 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/INSIDE-HAIR/mcp-google-calendar-and-meet'

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