@pablosr/mcp-google-calendar 1.1.0 → 1.1.2

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.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { GoogleCalendarClient } from "./tools/google-calendar-client.js";
6
6
  import { registerGoogleCalendarTools } from "./tools/google-calendar-mcp.js";
7
7
  const server = new McpServer({
8
8
  name: "mcp-google-calendar",
9
- version: "1.1.0",
9
+ version: "1.1.2",
10
10
  });
11
11
  async function main() {
12
12
  console.error('Configurando Google Calendar API...');
@@ -4,12 +4,12 @@ export function registerGoogleCreateEvent(googleClient, server) {
4
4
  server.tool('calendar_create_event', 'Crea un nuevo evento en Google Calendar', {
5
5
  calendarId: z.string().optional().describe('ID del calendario (por defecto: primary)'),
6
6
  title: z.string().describe('Título del evento'),
7
- start: z.string().describe('Fecha/hora de inicio en formato ISO (ej: 2023-12-01T10:00:00Z)'),
8
- end: z.string().describe('Fecha/hora de fin en formato ISO'),
7
+ start: z.string().describe('Fecha/hora de inicio en formato ISO 8601 Zulu time (ej: 2023-12-01T10:00:00Z).'),
8
+ end: z.string().describe('Fecha/hora de fin en formato ISO 8601 Zulu time (ej: 2023-12-01T11:00:00Z).'),
9
9
  description: z.string().optional().describe('Descripción del evento (opcional)'),
10
10
  location: z.string().optional().describe('Ubicación del evento (opcional)'),
11
11
  attendees: z.array(z.string().email()).optional().describe('Lista de emails de los invitados (opcional)'),
12
- timeZone: z.string().optional().describe('Zona horaria en formato IANA (ej: America/New_York, Atlantic/Canary). Por defecto usa DEFAULT_TIMEZONE del .env'),
12
+ timeZone: z.string().optional().describe('Zona horaria en formato IANA (ej: America/New_York, Atlantic/Canary). Por defecto usa DEFAULT_TIMEZONE del .env. Esta zona se usa para interpretar la hora Zulu en el calendario'),
13
13
  }, async (args) => {
14
14
  try {
15
15
  const { calendarId = 'primary', title, start, end, description, location, attendees, timeZone, } = args;
@@ -1,14 +1,17 @@
1
1
  import { z } from 'zod';
2
+ import { validateAndNormalizeDate } from '../utils/date-validation.js';
2
3
  export function registerGoogleListEvents(googleClient, server) {
3
4
  server.tool('calendar_list_events', 'Lista eventos de un calendario de Google Calendar', {
4
5
  calendarId: z.string().optional().describe('ID del calendario (por defecto: primary)'),
5
- timeMin: z.string().optional().describe('Fecha/hora mínima en formato ISO (ej: 2023-12-01T00:00:00Z)'),
6
- timeMax: z.string().optional().describe('Fecha/hora máxima en formato ISO'),
6
+ timeMin: z.string().optional().describe('Fecha/hora mínima en formato ISO 8601 Zulu time (ej: 2023-12-01T00:00:00Z).'),
7
+ timeMax: z.string().optional().describe('Fecha/hora máxima en formato ISO 8601 Zulu time (ej: 2023-12-31T23:59:59Z).'),
7
8
  maxResults: z.number().optional().describe('Número máximo de resultados (por defecto: 10)'),
8
9
  }, async (args) => {
9
10
  try {
10
11
  const { calendarId = 'primary', timeMin, timeMax, maxResults = 10, } = args;
11
- const events = await googleClient.listEvents(calendarId, timeMin, timeMax, maxResults);
12
+ const normalizedTimeMin = timeMin ? validateAndNormalizeDate(timeMin) : undefined;
13
+ const normalizedTimeMax = timeMax ? validateAndNormalizeDate(timeMax) : undefined;
14
+ const events = await googleClient.listEvents(calendarId, normalizedTimeMin, normalizedTimeMax, maxResults);
12
15
  const data = events.map((event) => ({
13
16
  id: event.id,
14
17
  summary: event.summary,
@@ -1,15 +1,16 @@
1
1
  import { z } from 'zod';
2
+ import { validateAndNormalizeDate } from '../utils/date-validation.js';
2
3
  export function registerGoogleUpdateEvent(googleClient, server) {
3
4
  server.tool('calendar_update_event', 'Actualiza un evento existente en Google Calendar', {
4
5
  calendarId: z.string().optional().describe('ID del calendario (por defecto: primary)'),
5
6
  eventId: z.string().describe('ID del evento a actualizar'),
6
7
  title: z.string().optional().describe('Nuevo título del evento'),
7
- start: z.string().optional().describe('Nueva fecha/hora de inicio en formato ISO (ej: 2023-12-01T10:00:00Z)'),
8
- end: z.string().optional().describe('Nueva fecha/hora de fin en formato ISO'),
8
+ start: z.string().optional().describe('Nueva fecha/hora de inicio en formato ISO 8601 Zulu time (ej: 2023-12-01T10:00:00Z).'),
9
+ end: z.string().optional().describe('Nueva fecha/hora de fin en formato ISO 8601 Zulu time (ej: 2023-12-01T11:00:00Z).'),
9
10
  description: z.string().optional().describe('Nueva descripción del evento'),
10
11
  location: z.string().optional().describe('Nueva ubicación del evento'),
11
12
  attendees: z.array(z.string().email()).optional().describe('Lista de emails de los invitados (opcional)'),
12
- timeZone: z.string().optional().describe('Zona horaria en formato IANA (ej: America/New_York, Atlantic/Canary). Por defecto usa DEFAULT_TIMEZONE del .env'),
13
+ timeZone: z.string().optional().describe('Zona horaria en formato IANA (ej: America/New_York, Atlantic/Canary). Por defecto usa DEFAULT_TIMEZONE del .env. Esta zona se usa para interpretar la hora Zulu en el calendario'),
13
14
  }, async (args) => {
14
15
  try {
15
16
  const { calendarId = 'primary', eventId, title, start, end, description, location, attendees, timeZone, } = args;
@@ -37,14 +38,16 @@ export function registerGoogleUpdateEvent(googleClient, server) {
37
38
  }
38
39
  const defaultTimeZone = timeZone || process.env.DEFAULT_TIMEZONE || 'Atlantic/Canary';
39
40
  if (start !== undefined) {
41
+ const normalizedStart = validateAndNormalizeDate(start);
40
42
  event.start = {
41
- dateTime: start,
43
+ dateTime: normalizedStart,
42
44
  timeZone: defaultTimeZone,
43
45
  };
44
46
  }
45
47
  if (end !== undefined) {
48
+ const normalizedEnd = validateAndNormalizeDate(end);
46
49
  event.end = {
47
- dateTime: end,
50
+ dateTime: normalizedEnd,
48
51
  timeZone: defaultTimeZone,
49
52
  };
50
53
  }
@@ -1,4 +1,5 @@
1
1
  import { google } from 'googleapis';
2
+ import { validateAndNormalizeDate } from './utils/date-validation.js';
2
3
  export class GoogleCalendarClient {
3
4
  auth;
4
5
  calendar;
@@ -89,7 +90,7 @@ export class GoogleCalendarClient {
89
90
  */
90
91
  async updateEvent(calendarId = 'primary', eventId, event) {
91
92
  try {
92
- const response = await this.calendar.events.update({
93
+ const response = await this.calendar.events.patch({
93
94
  calendarId,
94
95
  eventId,
95
96
  requestBody: event,
@@ -237,19 +238,22 @@ export class GoogleCalendarClient {
237
238
  }
238
239
  /**
239
240
  * Función helper para crear eventos con formato simplificado
241
+ * Las fechas se normalizan a formato Zulu time (ISO 8601 UTC sin offset)
240
242
  */
241
243
  export function createSimpleEvent(title, start, end, description, location, attendees, timeZone) {
242
244
  const defaultTimeZone = timeZone || process.env.DEFAULT_TIMEZONE || 'Atlantic/Canary';
245
+ const normalizedStart = validateAndNormalizeDate(start);
246
+ const normalizedEnd = validateAndNormalizeDate(end);
243
247
  const event = {
244
248
  summary: title,
245
249
  description,
246
250
  location,
247
251
  start: {
248
- dateTime: start,
252
+ dateTime: normalizedStart,
249
253
  timeZone: defaultTimeZone,
250
254
  },
251
255
  end: {
252
- dateTime: end,
256
+ dateTime: normalizedEnd,
253
257
  timeZone: defaultTimeZone,
254
258
  },
255
259
  };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Valida si una fecha está en formato ISO 8601 Zulu time (sin offset)
3
+ * Formato esperado: YYYY-MM-DDTHH:mm:ss.sssZ o YYYY-MM-DDTHH:mm:ssZ
4
+ * @param dateString - String de fecha a validar
5
+ * @returns true si está en formato Zulu time, false en caso contrario
6
+ */
7
+ export function isZuluTime(dateString) {
8
+ const zuluTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/;
9
+ return zuluTimeRegex.test(dateString);
10
+ }
11
+ /**
12
+ * Convierte cualquier fecha ISO 8601 a Zulu time (UTC sin offset)
13
+ * Si la fecha ya está en Zulu time, la devuelve sin cambios
14
+ * Si la fecha tiene offset, lo elimina y convierte a UTC
15
+ * @param dateString - String de fecha en formato ISO 8601
16
+ * @returns String de fecha en formato Zulu time
17
+ * @throws Error si la fecha no es válida
18
+ */
19
+ export function convertToZuluTime(dateString) {
20
+ if (isZuluTime(dateString)) {
21
+ return dateString;
22
+ }
23
+ try {
24
+ const date = new Date(dateString);
25
+ if (isNaN(date.getTime())) {
26
+ throw new Error(`Fecha inválida: ${dateString}`);
27
+ }
28
+ return date.toISOString();
29
+ }
30
+ catch (error) {
31
+ throw new Error(`Error al convertir fecha a Zulu time: ${dateString}. ${error instanceof Error ? error.message : 'Error desconocido'}`);
32
+ }
33
+ }
34
+ /**
35
+ * Valida y normaliza una fecha a formato Zulu time
36
+ * @param dateString - String de fecha a validar y normalizar
37
+ * @returns String de fecha en formato Zulu time
38
+ * @throws Error si la fecha no es válida
39
+ */
40
+ export function validateAndNormalizeDate(dateString) {
41
+ if (!dateString || typeof dateString !== 'string') {
42
+ throw new Error('La fecha debe ser un string no vacío');
43
+ }
44
+ return convertToZuluTime(dateString);
45
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pablosr/mcp-google-calendar",
3
3
  "description": "A Google Calendar (MCP) server to expose calendar operations as tools for LLM.",
4
- "version": "1.1.0",
4
+ "version": "1.1.2",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "bin": "dist/index.js",