UNPKG

@coretext-ai/public-gsuite-mcp-d495ced0-92b0-4d0e-ad89-f27c9e31f4f9

Version:
1,721 lines 83.1 kB
import axios from 'axios';
import { Logger } from '../services/logger.js';
export class GoogleCalendarClient {
    constructor(config) {
        this.config = config;
        // Generate unique session ID for this client instance
        this.sessionId = `google-calendar-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
        // Initialize logger (fallback to console if not provided)
        this.logger = config.logger || new Logger({
            logLevel: 'ERROR',
            component: 'client',
            enableConsole: true,
            enableShipping: false,
            serverName: 'public-gsuite-mcp'
        });
        this.logger.info('CLIENT_INIT', 'Client instance created', {
            baseUrl: this.resolveBaseUrl(),
            timeout: this.config.timeout || 30000,
            hasRateLimit: !!this.config.rateLimit,
            configKeys: Object.keys(config)
        });
        // Initialize OAuth client from config if provided
        this.oauthClient = config.oauthClient;
        this.httpClient = axios.create({
            baseURL: this.resolveBaseUrl(),
            timeout: this.config.timeout || 30000,
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json',
                'User-Agent': 'public-gsuite-mcp/1.0.0',
                ...this.getAuthHeaders()
            },
        });
        // Add request interceptor for rate limiting
        if (this.config.rateLimit) {
            this.setupRateLimit(this.config.rateLimit);
        }
        // Add request interceptor for logging
        this.httpClient.interceptors.request.use((config) => {
            this.logger.logRequestStart(config.method?.toUpperCase() || 'GET', `${config.baseURL}${config.url}`, {
                hasData: !!config.data,
                hasParams: !!(config.params && Object.keys(config.params).length > 0),
                headers: Object.keys(config.headers || {})
            });
            if (config.data) {
                this.logger.debug('HTTP_REQUEST_BODY', 'Request body data', {
                    dataType: typeof config.data,
                    dataSize: JSON.stringify(config.data).length
                });
            }
            if (config.params && Object.keys(config.params).length > 0) {
                this.logger.debug('HTTP_REQUEST_PARAMS', 'Query parameters', {
                    paramCount: Object.keys(config.params).length,
                    paramKeys: Object.keys(config.params)
                });
            }
            return config;
        }, (error) => {
            this.logger.error('HTTP_REQUEST_ERROR', 'Request interceptor error', {
                error: error.message,
                code: error.code
            });
            return Promise.reject(error);
        });
        // Add response interceptor for logging and error handling
        this.httpClient.interceptors.response.use((response) => {
            this.logger.logRequestSuccess(response.config?.method?.toUpperCase() || 'GET', `${response.config?.baseURL}${response.config?.url}`, response.status, 0, // Duration will be calculated in endpoint methods
            {
                statusText: response.statusText,
                responseSize: JSON.stringify(response.data).length,
                headers: Object.keys(response.headers || {})
            });
            return response;
        }, (error) => {
            this.logger.logRequestError(error.config?.method?.toUpperCase() || 'GET', `${error.config?.baseURL}${error.config?.url}`, error, 0, // Duration will be calculated in endpoint methods
            {
                hasResponseData: !!error.response?.data
            });
            throw error;
        });
    }
    setupRateLimit(requestsPerMinute) {
        const interval = 60000 / requestsPerMinute; // ms between requests
        let lastRequestTime = 0;
        this.logger.info('RATE_LIMIT_SETUP', 'Rate limiting configured', {
            requestsPerMinute,
            intervalMs: interval
        });
        this.httpClient.interceptors.request.use(async (config) => {
            const now = Date.now();
            const timeSinceLastRequest = now - lastRequestTime;
            if (timeSinceLastRequest < interval) {
                const delayMs = interval - timeSinceLastRequest;
                this.logger.logRateLimit('HTTP_REQUEST', delayMs, {
                    timeSinceLastRequest,
                    requiredInterval: interval
                });
                await new Promise(resolve => setTimeout(resolve, delayMs));
            }
            lastRequestTime = Date.now();
            return config;
        });
    }
    resolveBaseUrl() {
        let baseUrl = 'https://www.googleapis.com/calendar/v3';
        // Handle dynamic domain replacement (e.g., YOUR_DOMAIN placeholder)
        if (baseUrl.includes('YOUR_DOMAIN')) {
            const domainEnvVar = `GOOGLE_CALENDAR_DOMAIN`;
            const domain = process.env[domainEnvVar];
            if (!domain) {
                throw new Error(`Missing domain configuration. Please set ${domainEnvVar} environment variable.`);
            }
            baseUrl = baseUrl.replace('YOUR_DOMAIN', domain);
            console.error(`[GOOGLE_CALENDAR] Resolved base URL: ${baseUrl}`);
        }
        return baseUrl;
    }
    getAuthHeaders() {
        // OAuth authentication (both ConstructionWire and standard OAuth) - handled dynamically
        // Tokens will be applied asynchronously via makeAuthenticatedRequest
        this.logger.logAuthEvent('oauth_auth_setup', true, {
            authType: 'oauth2',
            message: 'OAuth tokens will be applied dynamically during requests',
            oauthClientPresent: !!this.oauthClient
        });
        return {};
    }
    /**
     * Initialize the client (for OAuth clients that need initialization)
     */
    async initialize() {
        if (this.oauthClient) {
            await this.oauthClient.initialize();
            this.logger.info('CLIENT_INITIALIZE', 'OAuth client initialized');
        }
    }
    /**
     * Get the session ID for this client instance
     */
    getSessionId() {
        return this.sessionId;
    }
    /**
     * Make an authenticated request with proper headers
     */
    async makeAuthenticatedRequest(config) {
        // Get OAuth token for standard OAuth
        this.logger.info('REQUEST_AUTH', 'Applying standard OAuth authentication', {
            authType: 'oauth2',
            requestUrl: config.url,
            hasOAuthClient: !!this.oauthClient
        });
        if (this.oauthClient) {
            const accessToken = await this.oauthClient.getValidAccessToken();
            config.headers = {
                ...config.headers,
                'Authorization': `Bearer ${accessToken}`
            };
            this.logger.logAuthEvent('oauth_token_applied', true, {
                authType: 'oauth2',
                tokenPreview: accessToken ? accessToken.substring(0, 8) + '...' : 'null',
                header: 'Authorization',
                tokenSource: 'standard_oauth',
                finalHeaders: Object.keys(config.headers)
            });
        }
        else {
            this.logger.warn('OAUTH_CLIENT_MISSING', 'OAuth client not available for OAuth-enabled template', {
                authType: 'oauth2',
                requestUrl: config.url
            });
        }
        return this.httpClient.request(config);
    }
    buildPath(template, params) {
        let path = template;
        // Custom encoding that preserves forward slashes for API paths
        const encodePathComponent = (value) => {
            // For Google API resource names like "people/c123", preserve the forward slash
            return encodeURIComponent(value).replace(/%2F/g, '/');
        };
        // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
        const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
        let match;
        const processedParams = [];
        while ((match = googlePathTemplateRegex.exec(template)) !== null) {
            const fullMatch = match[0]; // e.g., "{resourceName=people/*}"
            const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
            if (paramName && params[paramName] !== undefined) {
                path = path.replace(fullMatch, encodePathComponent(String(params[paramName])));
                processedParams.push(paramName);
            }
        }
        // Handle standard path templates: {resourceName}
        for (const [key, value] of Object.entries(params)) {
            if (!processedParams.includes(key)) {
                const standardTemplate = `{${key}}`;
                if (path.includes(standardTemplate)) {
                    path = path.replace(standardTemplate, encodePathComponent(String(value)));
                    processedParams.push(key);
                }
            }
        }
        this.logger.debug('PATH_BUILD', 'Built API path from template', {
            template,
            resultPath: path,
            paramCount: Object.keys(params).length,
            paramKeys: Object.keys(params),
            processedParams,
            hasGoogleTemplates: googlePathTemplateRegex.test(template)
        });
        return path;
    }
    async listCalendars(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'list_calendars',
            method: 'GET',
            path: '/users/me/calendarList',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/users/me/calendarList';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["maxResults"] !== undefined) {
                queryParams["maxResults"] = params["maxResults"];
                extractedParams.push("maxResults");
            }
            if (params["minAccessRole"] !== undefined) {
                queryParams["minAccessRole"] = params["minAccessRole"];
                extractedParams.push("minAccessRole");
            }
            if (params["pageToken"] !== undefined) {
                queryParams["pageToken"] = params["pageToken"];
                extractedParams.push("pageToken");
            }
            if (params["showDeleted"] !== undefined) {
                queryParams["showDeleted"] = params["showDeleted"];
                extractedParams.push("showDeleted");
            }
            if (params["showHidden"] !== undefined) {
                queryParams["showHidden"] = params["showHidden"];
                extractedParams.push("showHidden");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            const path = this.buildPath('/users/me/calendarList', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'GET', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'list_calendars',
                method: 'GET',
                path: '/users/me/calendarList',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'list_calendars',
                method: 'GET',
                path: '/users/me/calendarList',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute list_calendars: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async getCalendar(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'get_calendar',
            method: 'GET',
            path: '/calendars/{calendarId}',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/calendars/{calendarId}';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.calendarId) {
                throw new Error(`Missing required parameter: calendarId`);
            }
            const path = this.buildPath('/calendars/{calendarId}', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'GET', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'get_calendar',
                method: 'GET',
                path: '/calendars/{calendarId}',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'get_calendar',
                method: 'GET',
                path: '/calendars/{calendarId}',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute get_calendar: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async createCalendar(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'create_calendar',
            method: 'POST',
            path: '/calendars',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/calendars';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["summary"] !== undefined) {
                bodyParams["summary"] = params["summary"];
                extractedParams.push("summary");
            }
            if (params["description"] !== undefined) {
                bodyParams["description"] = params["description"];
                extractedParams.push("description");
            }
            if (params["location"] !== undefined) {
                bodyParams["location"] = params["location"];
                extractedParams.push("location");
            }
            if (params["timeZone"] !== undefined) {
                bodyParams["timeZone"] = params["timeZone"];
                extractedParams.push("timeZone");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.summary) {
                throw new Error(`Missing required parameter: summary`);
            }
            const path = this.buildPath('/calendars', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'POST', url: path, params: queryParams, data: Object.keys(bodyParams).length > 0 ? bodyParams : undefined });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'create_calendar',
                method: 'POST',
                path: '/calendars',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'create_calendar',
                method: 'POST',
                path: '/calendars',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute create_calendar: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async updateCalendar(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'update_calendar',
            method: 'PUT',
            path: '/calendars/{calendarId}',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/calendars/{calendarId}';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["summary"] !== undefined) {
                bodyParams["summary"] = params["summary"];
                extractedParams.push("summary");
            }
            if (params["description"] !== undefined) {
                bodyParams["description"] = params["description"];
                extractedParams.push("description");
            }
            if (params["location"] !== undefined) {
                bodyParams["location"] = params["location"];
                extractedParams.push("location");
            }
            if (params["timeZone"] !== undefined) {
                bodyParams["timeZone"] = params["timeZone"];
                extractedParams.push("timeZone");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.calendarId) {
                throw new Error(`Missing required parameter: calendarId`);
            }
            const path = this.buildPath('/calendars/{calendarId}', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'PUT', url: path, params: queryParams, data: Object.keys(bodyParams).length > 0 ? bodyParams : undefined });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'update_calendar',
                method: 'PUT',
                path: '/calendars/{calendarId}',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'update_calendar',
                method: 'PUT',
                path: '/calendars/{calendarId}',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute update_calendar: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async deleteCalendar(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'delete_calendar',
            method: 'DELETE',
            path: '/calendars/{calendarId}',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/calendars/{calendarId}';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.calendarId) {
                throw new Error(`Missing required parameter: calendarId`);
            }
            const path = this.buildPath('/calendars/{calendarId}', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'DELETE', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'delete_calendar',
                method: 'DELETE',
                path: '/calendars/{calendarId}',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'delete_calendar',
                method: 'DELETE',
                path: '/calendars/{calendarId}',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute delete_calendar: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async listEvents(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'list_events',
            method: 'GET',
            path: '/calendars/{calendarId}/events',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/calendars/{calendarId}/events';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["maxResults"] !== undefined) {
                queryParams["maxResults"] = params["maxResults"];
                extractedParams.push("maxResults");
            }
            if (params["orderBy"] !== undefined) {
                queryParams["orderBy"] = params["orderBy"];
                extractedParams.push("orderBy");
            }
            if (params["pageToken"] !== undefined) {
                queryParams["pageToken"] = params["pageToken"];
                extractedParams.push("pageToken");
            }
            if (params["q"] !== undefined) {
                queryParams["q"] = params["q"];
                extractedParams.push("q");
            }
            if (params["showDeleted"] !== undefined) {
                queryParams["showDeleted"] = params["showDeleted"];
                extractedParams.push("showDeleted");
            }
            if (params["singleEvents"] !== undefined) {
                queryParams["singleEvents"] = params["singleEvents"];
                extractedParams.push("singleEvents");
            }
            if (params["timeMax"] !== undefined) {
                queryParams["timeMax"] = params["timeMax"];
                extractedParams.push("timeMax");
            }
            if (params["timeMin"] !== undefined) {
                queryParams["timeMin"] = params["timeMin"];
                extractedParams.push("timeMin");
            }
            if (params["timeZone"] !== undefined) {
                queryParams["timeZone"] = params["timeZone"];
                extractedParams.push("timeZone");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.calendarId) {
                throw new Error(`Missing required parameter: calendarId`);
            }
            const path = this.buildPath('/calendars/{calendarId}/events', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'GET', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'list_events',
                method: 'GET',
                path: '/calendars/{calendarId}/events',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'list_events',
                method: 'GET',
                path: '/calendars/{calendarId}/events',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute list_events: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async getEvent(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'get_event',
            method: 'GET',
            path: '/calendars/{calendarId}/events/{eventId}',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/calendars/{calendarId}/events/{eventId}';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["timeZone"] !== undefined) {
                queryParams["timeZone"] = params["timeZone"];
                extractedParams.push("timeZone");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.calendarId) {
                throw new Error(`Missing required parameter: calendarId`);
            }
            if (!params.eventId) {
                throw new Error(`Missing required parameter: eventId`);
            }
            const path = this.buildPath('/calendars/{calendarId}/events/{eventId}', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'GET', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'get_event',
                method: 'GET',
                path: '/calendars/{calendarId}/events/{eventId}',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'get_event',
                method: 'GET',
                path: '/calendars/{calendarId}/events/{eventId}',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute get_event: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async createEvent(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'create_event',
            method: 'POST',
            path: '/calendars/{calendarId}/events',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/calendars/{calendarId}/events';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["summary"] !== undefined) {
                bodyParams["summary"] = params["summary"];
                extractedParams.push("summary");
            }
            if (params["description"] !== undefined) {
                bodyParams["description"] = params["description"];
                extractedParams.push("description");
            }
            if (params["location"] !== undefined) {
                bodyParams["location"] = params["location"];
                extractedParams.push("location");
            }
            if (params["start"] !== undefined) {
                bodyParams["start"] = params["start"];
                extractedParams.push("start");
            }
            if (params["end"] !== undefined) {
                bodyParams["end"] = params["end"];
                extractedParams.push("end");
            }
            if (params["attendees"] !== undefined) {
                bodyParams["attendees"] = params["attendees"];
                extractedParams.push("attendees");
            }
            if (params["reminders"] !== undefined) {
                bodyParams["reminders"] = params["reminders"];
                extractedParams.push("reminders");
            }
            if (params["recurrence"] !== undefined) {
                bodyParams["recurrence"] = params["recurrence"];
                extractedParams.push("recurrence");
            }
            if (params["sendNotifications"] !== undefined) {
                queryParams["sendNotifications"] = params["sendNotifications"];
                extractedParams.push("sendNotifications");
            }
            if (params["sendUpdates"] !== undefined) {
                queryParams["sendUpdates"] = params["sendUpdates"];
                extractedParams.push("sendUpdates");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.calendarId) {
                throw new Error(`Missing required parameter: calendarId`);
            }
            if (!params.summary) {
                throw new Error(`Missing required parameter: summary`);
            }
            if (!params.start) {
                throw new Error(`Missing required parameter: start`);
            }
            if (!params.end) {
                throw new Error(`Missing required parameter: end`);
            }
            const path = this.buildPath('/calendars/{calendarId}/events', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'POST', url: path, params: queryParams, data: Object.keys(bodyParams).length > 0 ? bodyParams : undefined });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'create_event',
                method: 'POST',
                path: '/calendars/{calendarId}/events',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'create_event',
                method: 'POST',
                path: '/calendars/{calendarId}/events',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute create_event: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async updateEvent(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'update_event',
            method: 'PUT',
            path: '/calendars/{calendarId}/events/{eventId}',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/calendars/{calendarId}/events/{eventId}';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["summary"] !== undefined) {
                bodyParams["summary"] = params["summary"];
                extractedParams.push("summary");
            }
            if (params["description"] !== undefined) {
                bodyParams["description"] = params["description"];
                extractedParams.push("description");
            }
            if (params["location"] !== undefined) {
                bodyParams["location"] = params["location"];
                extractedParams.push("location");
            }
            if (params["start"] !== undefined) {
                bodyParams["start"] = params["start"];
                extractedParams.push("start");
            }
            if (params["end"] !== undefined) {
                bodyParams["end"] = params["end"];
                extractedParams.push("end");
            }
            if (params["attendees"] !== undefined) {
                bodyParams["attendees"] = params["attendees"];
                extractedParams.push("attendees");
            }
            if (params["sendNotifications"] !== undefined) {
                queryParams["sendNotifications"] = params["sendNotifications"];
                extractedParams.push("sendNotifications");
            }
            if (params["sendUpdates"] !== undefined) {
                queryParams["sendUpdates"] = params["sendUpdates"];
                extractedParams.push("sendUpdates");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.calendarId) {
                throw new Error(`Missing required parameter: calendarId`);
            }
            if (!params.eventId) {
                throw new Error(`Missing required parameter: eventId`);
            }
            if (!params.start) {
                throw new Error(`Missing required parameter: start`);
            }
            if (!params.end) {
                throw new Error(`Missing required parameter: end`);
            }
            const path = this.buildPath('/calendars/{calendarId}/events/{eventId}', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'PUT', url: path, params: queryParams, data: Object.keys(bodyParams).length > 0 ? bodyParams : undefined });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'update_event',
                method: 'PUT',
                path: '/calendars/{calendarId}/events/{eventId}',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'update_event',
                method: 'PUT',
                path: '/calendars/{calendarId}/events/{eventId}',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute update_event: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async deleteEvent(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'delete_event',
            method: 'DELETE',
            path: '/calendars/{calendarId}/events/{eventId}',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/calendars/{calendarId}/events/{eventId}';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["sendNotifications"] !== undefined) {
                queryParams["sendNotifications"] = params["sendNotifications"];
                extractedParams.push("sendNotifications");
            }
            if (params["sendUpdates"] !== undefined) {
                queryParams["sendUpdates"] = params["sendUpdates"];
                extractedParams.push("sendUpdates");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.calendarId) {
                throw new Error(`Missing required parameter: calendarId`);
            }
            if (!params.eventId) {
                throw new Error(`Missing required parameter: eventId`);
            }
            const path = this.buildPath('/calendars/{calendarId}/events/{eventId}', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'DELETE', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'delete_event',
                method: 'DELETE',
                path: '/calendars/{calendarId}/events/{eventId}',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'delete_event',
                method: 'DELETE',
                path: '/calendars/{calendarId}/events/{eventId}',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute delete_event: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async quickAdd(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'quick_add',
            method: 'POST',
            path: '/calendars/{calendarId}/events/quickAdd',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/calendars/{calendarId}/events/quickAdd';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["text"] !== undefined) {
                queryParams["text"] = params["text"];
                extractedParams.push("text");
            }
            if (params["sendNotifications"] !== undefined) {
                queryParams["sendNotifications"] = params["sendNotifications"];
                extractedParams.push("sendNotifications");
            }
            if (params["sendUpdates"] !== undefined) {
                queryParams["sendUpdates"] = params["sendUpdates"];
                extractedParams.push("sendUpdates");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.calendarId) {
                throw new Error(`Missing required parameter: calendarId`);
            }
            if (!params.text) {
                throw new Error(`Missing required parameter: text`);
            }
            const path = this.buildPath('/calendars/{calendarId}/events/quickAdd', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'POST', url: path, params: queryParams, data: Object.keys(bodyParams).length > 0 ? bodyParams : undefined });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'quick_add',
                method: 'POST',
                path: '/calendars/{calendarId}/events/quickAdd',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'quick_add',
                method: 'POST',
                path: '/calendars/{calendarId}/events/quickAdd',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute quick_add: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async getFreeBusy(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'get_free_busy',
            method: 'POST',
            path: '/freeBusy',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/freeBusy';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["timeMin"] !== undefined) {
                bodyParams["timeMin"] = params["timeMin"];
                extractedParams.push("timeMin");
            }
            if (params["timeMax"] !== undefined) {
                bodyParams["timeMax"] = params["timeMax"];
                extractedParams.push("timeMax");
            }
            if (params["timeZone"] !== undefined) {
                bodyParams["timeZone"] = params["timeZone"];
                extractedParams.push("timeZone");
            }
            if (params["items"] !== undefined) {
                bodyParams["items"] = params["items"];
                extractedParams.push("items");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.timeMin) {
                throw new Error(`Missing required parameter: timeMin`);
            }
            if (!params.timeMax) {
                throw new Error(`Missing required parameter: timeMax`);
            }
            if (!params.items) {
                throw new Error(`Missing required parameter: items`);
            }
            const path = this.buildPath('/freeBusy', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'POST', url: path, params: queryParams, data: Object.keys(bodyParams).length > 0 ? bodyParams : undefined });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'get_free_busy',
                method: 'POST',
                path: '/freeBusy',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'get_free_busy',
                method: 'POST',
                path: '/freeBusy',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute get_free_busy: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async listAcl(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'list_acl',
            method: 'GET',
            path: '/calendars/{calendarId}/acl',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/calendars/{calendarId}/acl';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["maxResults"] !== undefined) {
                queryParams["maxResults"] = params["maxResults"];
                extractedParams.push("maxResults");
            }
            if (params["pageToken"] !== undefined) {
                queryParams["pageToken"] = params["pageToken"];
                extractedParams.push("pageToken");
            }
            if (params["showDeleted"] !== undefined) {
                queryParams["showDeleted"] = params["showDeleted"];
                extractedParams.push("showDeleted");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.calendarId) {
                throw new Error(`Missing required parameter: calendarId`);
            }
            const path = this.buildPath('/calendars/{calendarId}/acl', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'GET', url: path, params: queryParams });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'list_acl',
                method: 'GET',
                path: '/calendars/{calendarId}/acl',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'list_acl',
                method: 'GET',
                path: '/calendars/{calendarId}/acl',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute list_acl: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
    async createAcl(params) {
        const startTime = Date.now();
        this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
            endpoint: 'create_acl',
            method: 'POST',
            path: '/calendars/{calendarId}/acl',
            paramCount: Object.keys(params || {}).length,
            paramKeys: Object.keys(params || {})
        });
        try {
            // Extract and separate parameters by location: path, query, body
            const pathTemplate = '/calendars/{calendarId}/acl';
            const pathParams = {};
            const queryParams = {};
            const bodyParams = {};
            const extractedParams = [];
            // Handle Google-style path templates: {resourceName=people/*} and {person.resourceName=people/*}
            const googlePathTemplateRegex = /{([^}=]+)=[^}]*}/g;
            let match;
            while ((match = googlePathTemplateRegex.exec(pathTemplate)) !== null) {
                const paramName = match[1]; // e.g., "resourceName" or "person.resourceName"
                if (paramName && params[paramName] !== undefined) {
                    pathParams[paramName] = params[paramName];
                    extractedParams.push(paramName);
                }
            }
            // Handle standard path templates: {resourceName}
            const standardPathParams = pathTemplate.match(/{([^}=]+)}/g) || [];
            standardPathParams.forEach(paramTemplate => {
                const paramName = paramTemplate.slice(1, -1); // Remove { }
                // Only process if not already handled by Google template logic
                if (!extractedParams.includes(paramName)) {
                    if (params[paramName] !== undefined) {
                        pathParams[paramName] = params[paramName];
                        extractedParams.push(paramName);
                    }
                    else {
                        // Provide default values for optional path parameters
                        if (paramName === 'userId') {
                            pathParams[paramName] = 'me'; // Default to authenticated user
                            extractedParams.push(paramName);
                        }
                    }
                }
            });
            // Separate remaining parameters by location (query vs body)
            if (params["role"] !== undefined) {
                bodyParams["role"] = params["role"];
                extractedParams.push("role");
            }
            if (params["scope"] !== undefined) {
                bodyParams["scope"] = params["scope"];
                extractedParams.push("scope");
            }
            if (params["sendNotifications"] !== undefined) {
                queryParams["sendNotifications"] = params["sendNotifications"];
                extractedParams.push("sendNotifications");
            }
            // Any remaining unprocessed parameters default to body for backward compatibility
            for (const [key, value] of Object.entries(params)) {
                if (!extractedParams.includes(key)) {
                    bodyParams[key] = value;
                }
            }
            // Validate required parameters
            if (!params.calendarId) {
                throw new Error(`Missing required parameter: calendarId`);
            }
            if (!params.role) {
                throw new Error(`Missing required parameter: role`);
            }
            if (!params.scope) {
                throw new Error(`Missing required parameter: scope`);
            }
            const path = this.buildPath('/calendars/{calendarId}/acl', pathParams);
            // Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
            const response = await this.makeAuthenticatedRequest({ method: 'POST', url: path, params: queryParams, data: Object.keys(bodyParams).length > 0 ? bodyParams : undefined });
            const duration = Date.now() - startTime;
            this.logger.info('ENDPOINT_SUCCESS', 'Endpoint execution completed successfully', {
                endpoint: 'create_acl',
                method: 'POST',
                path: '/calendars/{calendarId}/acl',
                duration_ms: duration,
                responseDataSize: JSON.stringify(response.data).length
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify(response.data, null, 2)
                    }
                ]
            };
        }
        catch (error) {
            const duration = Date.now() - startTime;
            this.logger.error('ENDPOINT_ERROR', 'Endpoint execution failed', {
                endpoint: 'create_acl',
                method: 'POST',
                path: '/calendars/{calendarId}/acl',
                duration_ms: duration,
                error: error instanceof Error ? error.message : String(error),
                errorType: error instanceof Error ? error.constructor.name : 'unknown'
            });
            throw new Error(`Failed to execute create_acl: ${error instanceof Error ? error.message : String(error)}`);
        }
    }
}
//# sourceMappingURL=google-calendar-client.js.map