@coretext-ai/qa-gsuite-f47ac10b-58cc-4372-a567-0e02b2c3d479
Version:
MCP server with GSuite (Contacts, Drive, Gmail, Calendar) integration
515 lines • 19.4 kB
JavaScript
import { GoogleOAuthClient } from '../oauth/google-oauth-client.js';
export class GoogleCalendarClient {
constructor() {
this.baseUrl = 'https://www.googleapis.com/calendar/v3';
// Generate unique session ID for this client instance
this.sessionId = `google-calendar-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
this.logDebug('INIT', 'OAuth client instance created', {
baseUrl: this.baseUrl,
isOAuth: true
});
this.oauthClient = new GoogleOAuthClient();
}
/**
* Get the session ID for this client instance
*/
getSessionId() {
return this.sessionId;
}
/**
* Enhanced debug logging with session context
*/
logDebug(action, message, metadata) {
const timestamp = new Date().toISOString();
const user = process.env.CORETEXT_USER || 'unknown';
const logEntry = {
timestamp,
sessionId: this.sessionId,
user,
integration: 'google-calendar',
component: 'oauth-client',
action,
message,
...(metadata && { metadata })
};
// Use stderr to avoid MCP protocol interference
console.error(`[GOOGLE_CALENDAR-OAUTH-CLIENT] ${JSON.stringify(logEntry)}`);
}
/**
* Initialize the API client
*/
async initialize() {
this.logDebug('INITIALIZE', 'Starting OAuth client initialization', {
hasOAuthClient: true
});
await this.oauthClient.initialize();
this.logDebug('INITIALIZE', 'Google OAuth client initialization completed');
}
/**
* Make authenticated API request
*/
async makeRequest(config) {
const startTime = Date.now();
this.logDebug('REQUEST_START', 'Making authenticated API request', {
method: config.method,
path: config.path,
hasBody: !!config.body,
hasQueryParams: !!config.queryParams
});
const accessToken = await this.oauthClient.getValidAccessToken();
this.logDebug('AUTH_TOKEN', 'Retrieved Google OAuth access token', {
tokenPreview: accessToken ? accessToken.substring(0, 8) + '...' : 'none'
});
const headers = {
'Accept': 'application/json',
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
...config.headers
};
const url = this.buildUrl(config.path, config.pathParams, config.queryParams);
const response = await fetch(url, {
method: config.method,
headers,
body: config.body ? JSON.stringify(config.body) : undefined
});
if (!response.ok) {
if (response.status === 401) {
// Token might be invalid, try to refresh and retry once
await this.oauthClient.getValidAccessToken(); // This will refresh if needed
return this.makeRequest(config); // Retry once
}
const errorText = await response.text();
throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`);
}
return response.json();
}
/**
* Returns the calendars on the user's calendar list
*/
async listCalendars(args = {}) {
const pathParams = {};
const queryParams = {
...(args.maxResults !== undefined && { maxResults: args.maxResults }),
...(args.minAccessRole !== undefined && { minAccessRole: args.minAccessRole }),
...(args.pageToken !== undefined && { pageToken: args.pageToken }),
...(args.showDeleted !== undefined && { showDeleted: args.showDeleted }),
...(args.showHidden !== undefined && { showHidden: args.showHidden }),
};
const bodyParams = {};
// Remove undefined values from pathParams
Object.keys(pathParams).forEach(key => {
if (pathParams[key] === undefined) {
delete pathParams[key];
}
});
return this.makeRequest({
method: 'GET',
path: '/users/me/calendarList',
pathParams,
queryParams: Object.keys(queryParams).length ? queryParams : undefined,
body: Object.keys(bodyParams).length ? bodyParams : undefined
});
}
/**
* Returns metadata for a calendar
*/
async getCalendar(args = {}) {
const pathParams = {
calendarId: args.calendarId,
};
const queryParams = {};
const bodyParams = {};
// Remove undefined values from pathParams
Object.keys(pathParams).forEach(key => {
if (pathParams[key] === undefined) {
delete pathParams[key];
}
});
return this.makeRequest({
method: 'GET',
path: '/calendars/{calendarId}',
pathParams,
queryParams: Object.keys(queryParams).length ? queryParams : undefined,
body: Object.keys(bodyParams).length ? bodyParams : undefined
});
}
/**
* Creates a secondary calendar
*/
async createCalendar(args = {}) {
const pathParams = {};
const queryParams = {};
const bodyParams = {
summary: args.summary,
...(args.description !== undefined && { description: args.description }),
...(args.location !== undefined && { location: args.location }),
...(args.timeZone !== undefined && { timeZone: args.timeZone }),
};
// Remove undefined values from pathParams
Object.keys(pathParams).forEach(key => {
if (pathParams[key] === undefined) {
delete pathParams[key];
}
});
return this.makeRequest({
method: 'POST',
path: '/calendars',
pathParams,
queryParams: Object.keys(queryParams).length ? queryParams : undefined,
body: Object.keys(bodyParams).length ? bodyParams : undefined
});
}
/**
* Updates metadata for a calendar
*/
async updateCalendar(args = {}) {
const pathParams = {
calendarId: args.calendarId,
};
const queryParams = {};
const bodyParams = {
...(args.summary !== undefined && { summary: args.summary }),
...(args.description !== undefined && { description: args.description }),
...(args.location !== undefined && { location: args.location }),
...(args.timeZone !== undefined && { timeZone: args.timeZone }),
};
// Remove undefined values from pathParams
Object.keys(pathParams).forEach(key => {
if (pathParams[key] === undefined) {
delete pathParams[key];
}
});
return this.makeRequest({
method: 'PUT',
path: '/calendars/{calendarId}',
pathParams,
queryParams: Object.keys(queryParams).length ? queryParams : undefined,
body: Object.keys(bodyParams).length ? bodyParams : undefined
});
}
/**
* Deletes a secondary calendar
*/
async deleteCalendar(args = {}) {
const pathParams = {
calendarId: args.calendarId,
};
const queryParams = {};
const bodyParams = {};
// Remove undefined values from pathParams
Object.keys(pathParams).forEach(key => {
if (pathParams[key] === undefined) {
delete pathParams[key];
}
});
return this.makeRequest({
method: 'DELETE',
path: '/calendars/{calendarId}',
pathParams,
queryParams: Object.keys(queryParams).length ? queryParams : undefined,
body: Object.keys(bodyParams).length ? bodyParams : undefined
});
}
/**
* Returns events on the specified calendar
*/
async listEvents(args = {}) {
const pathParams = {
calendarId: args.calendarId,
};
const queryParams = {
...(args.maxResults !== undefined && { maxResults: args.maxResults }),
...(args.orderBy !== undefined && { orderBy: args.orderBy }),
...(args.pageToken !== undefined && { pageToken: args.pageToken }),
...(args.q !== undefined && { q: args.q }),
...(args.showDeleted !== undefined && { showDeleted: args.showDeleted }),
...(args.singleEvents !== undefined && { singleEvents: args.singleEvents }),
...(args.timeMax !== undefined && { timeMax: args.timeMax }),
...(args.timeMin !== undefined && { timeMin: args.timeMin }),
...(args.timeZone !== undefined && { timeZone: args.timeZone }),
};
const bodyParams = {};
// Remove undefined values from pathParams
Object.keys(pathParams).forEach(key => {
if (pathParams[key] === undefined) {
delete pathParams[key];
}
});
return this.makeRequest({
method: 'GET',
path: '/calendars/{calendarId}/events',
pathParams,
queryParams: Object.keys(queryParams).length ? queryParams : undefined,
body: Object.keys(bodyParams).length ? bodyParams : undefined
});
}
/**
* Returns an event
*/
async getEvent(args = {}) {
const pathParams = {
calendarId: args.calendarId,
eventId: args.eventId,
};
const queryParams = {
...(args.timeZone !== undefined && { timeZone: args.timeZone }),
};
const bodyParams = {};
// Remove undefined values from pathParams
Object.keys(pathParams).forEach(key => {
if (pathParams[key] === undefined) {
delete pathParams[key];
}
});
return this.makeRequest({
method: 'GET',
path: '/calendars/{calendarId}/events/{eventId}',
pathParams,
queryParams: Object.keys(queryParams).length ? queryParams : undefined,
body: Object.keys(bodyParams).length ? bodyParams : undefined
});
}
/**
* Creates an event
*/
async createEvent(args = {}) {
const pathParams = {
calendarId: args.calendarId,
};
const queryParams = {
...(args.sendNotifications !== undefined && { sendNotifications: args.sendNotifications }),
...(args.sendUpdates !== undefined && { sendUpdates: args.sendUpdates }),
};
const bodyParams = {
summary: args.summary,
...(args.description !== undefined && { description: args.description }),
...(args.location !== undefined && { location: args.location }),
start: args.start,
end: args.end,
...(args.attendees !== undefined && { attendees: args.attendees }),
...(args.reminders !== undefined && { reminders: args.reminders }),
...(args.recurrence !== undefined && { recurrence: args.recurrence }),
};
// Remove undefined values from pathParams
Object.keys(pathParams).forEach(key => {
if (pathParams[key] === undefined) {
delete pathParams[key];
}
});
return this.makeRequest({
method: 'POST',
path: '/calendars/{calendarId}/events',
pathParams,
queryParams: Object.keys(queryParams).length ? queryParams : undefined,
body: Object.keys(bodyParams).length ? bodyParams : undefined
});
}
/**
* Updates an event
*/
async updateEvent(args = {}) {
const pathParams = {
calendarId: args.calendarId,
eventId: args.eventId,
};
const queryParams = {
...(args.sendNotifications !== undefined && { sendNotifications: args.sendNotifications }),
...(args.sendUpdates !== undefined && { sendUpdates: args.sendUpdates }),
};
const bodyParams = {
...(args.summary !== undefined && { summary: args.summary }),
...(args.description !== undefined && { description: args.description }),
...(args.location !== undefined && { location: args.location }),
...(args.start !== undefined && { start: args.start }),
...(args.end !== undefined && { end: args.end }),
...(args.attendees !== undefined && { attendees: args.attendees }),
};
// Remove undefined values from pathParams
Object.keys(pathParams).forEach(key => {
if (pathParams[key] === undefined) {
delete pathParams[key];
}
});
return this.makeRequest({
method: 'PUT',
path: '/calendars/{calendarId}/events/{eventId}',
pathParams,
queryParams: Object.keys(queryParams).length ? queryParams : undefined,
body: Object.keys(bodyParams).length ? bodyParams : undefined
});
}
/**
* Deletes an event
*/
async deleteEvent(args = {}) {
const pathParams = {
calendarId: args.calendarId,
eventId: args.eventId,
};
const queryParams = {
...(args.sendNotifications !== undefined && { sendNotifications: args.sendNotifications }),
...(args.sendUpdates !== undefined && { sendUpdates: args.sendUpdates }),
};
const bodyParams = {};
// Remove undefined values from pathParams
Object.keys(pathParams).forEach(key => {
if (pathParams[key] === undefined) {
delete pathParams[key];
}
});
return this.makeRequest({
method: 'DELETE',
path: '/calendars/{calendarId}/events/{eventId}',
pathParams,
queryParams: Object.keys(queryParams).length ? queryParams : undefined,
body: Object.keys(bodyParams).length ? bodyParams : undefined
});
}
/**
* Creates an event based on a simple text string
*/
async quickAdd(args = {}) {
const pathParams = {
calendarId: args.calendarId,
};
const queryParams = {
text: args.text,
...(args.sendNotifications !== undefined && { sendNotifications: args.sendNotifications }),
...(args.sendUpdates !== undefined && { sendUpdates: args.sendUpdates }),
};
const bodyParams = {};
// Remove undefined values from pathParams
Object.keys(pathParams).forEach(key => {
if (pathParams[key] === undefined) {
delete pathParams[key];
}
});
return this.makeRequest({
method: 'POST',
path: '/calendars/{calendarId}/events/quickAdd',
pathParams,
queryParams: Object.keys(queryParams).length ? queryParams : undefined,
body: Object.keys(bodyParams).length ? bodyParams : undefined
});
}
/**
* Returns free/busy information for a set of calendars
*/
async getFreeBusy(args = {}) {
const pathParams = {};
const queryParams = {};
const bodyParams = {
timeMin: args.timeMin,
timeMax: args.timeMax,
...(args.timeZone !== undefined && { timeZone: args.timeZone }),
items: args.items,
};
// Remove undefined values from pathParams
Object.keys(pathParams).forEach(key => {
if (pathParams[key] === undefined) {
delete pathParams[key];
}
});
return this.makeRequest({
method: 'POST',
path: '/freeBusy',
pathParams,
queryParams: Object.keys(queryParams).length ? queryParams : undefined,
body: Object.keys(bodyParams).length ? bodyParams : undefined
});
}
/**
* Returns the rules in the access control list for the calendar
*/
async listAcl(args = {}) {
const pathParams = {
calendarId: args.calendarId,
};
const queryParams = {
...(args.maxResults !== undefined && { maxResults: args.maxResults }),
...(args.pageToken !== undefined && { pageToken: args.pageToken }),
...(args.showDeleted !== undefined && { showDeleted: args.showDeleted }),
};
const bodyParams = {};
// Remove undefined values from pathParams
Object.keys(pathParams).forEach(key => {
if (pathParams[key] === undefined) {
delete pathParams[key];
}
});
return this.makeRequest({
method: 'GET',
path: '/calendars/{calendarId}/acl',
pathParams,
queryParams: Object.keys(queryParams).length ? queryParams : undefined,
body: Object.keys(bodyParams).length ? bodyParams : undefined
});
}
/**
* Creates an access control rule
*/
async createAcl(args = {}) {
const pathParams = {
calendarId: args.calendarId,
};
const queryParams = {
...(args.sendNotifications !== undefined && { sendNotifications: args.sendNotifications }),
};
const bodyParams = {
role: args.role,
scope: args.scope,
};
// Remove undefined values from pathParams
Object.keys(pathParams).forEach(key => {
if (pathParams[key] === undefined) {
delete pathParams[key];
}
});
return this.makeRequest({
method: 'POST',
path: '/calendars/{calendarId}/acl',
pathParams,
queryParams: Object.keys(queryParams).length ? queryParams : undefined,
body: Object.keys(bodyParams).length ? bodyParams : undefined
});
}
/**
* Build URL with path parameters and query string
*/
buildUrl(path, pathParams, queryParams) {
let url = this.baseUrl + path;
// Replace path parameters
if (pathParams) {
for (const [key, value] of Object.entries(pathParams)) {
url = url.replace(`{${key}}`, encodeURIComponent(String(value)));
}
}
// Add query parameters
if (queryParams && Object.keys(queryParams).length > 0) {
const searchParams = new URLSearchParams();
for (const [key, value] of Object.entries(queryParams)) {
if (value !== undefined && value !== null) {
searchParams.append(key, String(value));
}
}
const queryString = searchParams.toString();
if (queryString) {
url += `?${queryString}`;
}
}
return url;
}
/**
* Check if client is authenticated
*/
async isAuthenticated() {
return this.oauthClient.isAuthenticated();
}
/**
* Revoke authentication tokens
*/
async revokeAuthentication() {
await this.oauthClient.revokeTokens();
}
}
//# sourceMappingURL=google-calendar-client.js.map