UNPKG

@coretext-ai/qa-gsuite-f47ac10b-58cc-4372-a567-0e02b2c3d479

Version:

MCP server with GSuite (Contacts, Drive, Gmail, Calendar) integration

765 lines 26.8 kB
import { GoogleOAuthClient } from '../oauth/google-oauth-client.js'; export class GoogleGmailClient { constructor() { this.baseUrl = 'https://gmail.googleapis.com/gmail/v1'; // Generate unique session ID for this client instance this.sessionId = `google-gmail-${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-gmail', component: 'oauth-client', action, message, ...(metadata && { metadata }) }; // Use stderr to avoid MCP protocol interference console.error(`[GOOGLE_GMAIL-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(); } /** * Send an email message. Requires message in RFC 2822 format encoded as base64url string */ async sendMessage(args = {}) { const pathParams = { userId: args.userId || 'me', }; const queryParams = {}; const bodyParams = { raw: args.raw, }; // Remove undefined values from pathParams Object.keys(pathParams).forEach(key => { if (pathParams[key] === undefined) { delete pathParams[key]; } }); return this.makeRequest({ method: 'POST', path: '/users/{userId}/messages/send', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Get a specific message by ID */ async getMessage(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; const queryParams = { ...(args.format !== undefined && { format: args.format }), ...(args.metadataHeaders !== undefined && { metadataHeaders: args.metadataHeaders }), }; 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/{userId}/messages/{id}', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * List messages in user's mailbox with optional filtering */ async listMessages(args = {}) { const pathParams = { userId: args.userId || 'me', }; const queryParams = { ...(args.q !== undefined && { q: args.q }), ...(args.labelIds !== undefined && { labelIds: args.labelIds }), ...(args.maxResults !== undefined && { maxResults: args.maxResults }), ...(args.pageToken !== undefined && { pageToken: args.pageToken }), ...(args.includeSpamTrash !== undefined && { includeSpamTrash: args.includeSpamTrash }), }; 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/{userId}/messages', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Modify labels on a message (add/remove labels, mark read/unread) */ async modifyMessage(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; const queryParams = {}; const bodyParams = { ...(args.addLabelIds !== undefined && { addLabelIds: args.addLabelIds }), ...(args.removeLabelIds !== undefined && { removeLabelIds: args.removeLabelIds }), }; // Remove undefined values from pathParams Object.keys(pathParams).forEach(key => { if (pathParams[key] === undefined) { delete pathParams[key]; } }); return this.makeRequest({ method: 'POST', path: '/users/{userId}/messages/{id}/modify', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Move a message to trash */ async trashMessage(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; 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: 'POST', path: '/users/{userId}/messages/{id}/trash', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Remove a message from trash */ async untrashMessage(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; 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: 'POST', path: '/users/{userId}/messages/{id}/untrash', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Permanently delete a message */ async deleteMessage(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; 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: '/users/{userId}/messages/{id}', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * List all labels in the user's mailbox */ async listLabels(args = {}) { const pathParams = { userId: args.userId || 'me', }; 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: '/users/{userId}/labels', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Get label details by ID */ async getLabel(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; 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: '/users/{userId}/labels/{id}', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Create a new custom label */ async createLabel(args = {}) { const pathParams = { userId: args.userId || 'me', }; const queryParams = {}; const bodyParams = { name: args.name, ...(args.labelListVisibility !== undefined && { labelListVisibility: args.labelListVisibility }), ...(args.messageListVisibility !== undefined && { messageListVisibility: args.messageListVisibility }), ...(args.color !== undefined && { color: args.color }), }; // Remove undefined values from pathParams Object.keys(pathParams).forEach(key => { if (pathParams[key] === undefined) { delete pathParams[key]; } }); return this.makeRequest({ method: 'POST', path: '/users/{userId}/labels', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Update an existing label */ async updateLabel(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; const queryParams = {}; const bodyParams = { ...(args.name !== undefined && { name: args.name }), ...(args.labelListVisibility !== undefined && { labelListVisibility: args.labelListVisibility }), ...(args.messageListVisibility !== undefined && { messageListVisibility: args.messageListVisibility }), ...(args.color !== undefined && { color: args.color }), }; // Remove undefined values from pathParams Object.keys(pathParams).forEach(key => { if (pathParams[key] === undefined) { delete pathParams[key]; } }); return this.makeRequest({ method: 'PUT', path: '/users/{userId}/labels/{id}', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Delete a custom label */ async deleteLabel(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; 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: '/users/{userId}/labels/{id}', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * List email threads in user's mailbox */ async listThreads(args = {}) { const pathParams = { userId: args.userId || 'me', }; const queryParams = { ...(args.q !== undefined && { q: args.q }), ...(args.labelIds !== undefined && { labelIds: args.labelIds }), ...(args.maxResults !== undefined && { maxResults: args.maxResults }), ...(args.pageToken !== undefined && { pageToken: args.pageToken }), ...(args.includeSpamTrash !== undefined && { includeSpamTrash: args.includeSpamTrash }), }; 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/{userId}/threads', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Get a specific thread by ID */ async getThread(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; const queryParams = { ...(args.format !== undefined && { format: args.format }), ...(args.metadataHeaders !== undefined && { metadataHeaders: args.metadataHeaders }), }; 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/{userId}/threads/{id}', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Modify labels on all messages in a thread */ async modifyThread(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; const queryParams = {}; const bodyParams = { ...(args.addLabelIds !== undefined && { addLabelIds: args.addLabelIds }), ...(args.removeLabelIds !== undefined && { removeLabelIds: args.removeLabelIds }), }; // Remove undefined values from pathParams Object.keys(pathParams).forEach(key => { if (pathParams[key] === undefined) { delete pathParams[key]; } }); return this.makeRequest({ method: 'POST', path: '/users/{userId}/threads/{id}/modify', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Move a thread to trash */ async trashThread(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; 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: 'POST', path: '/users/{userId}/threads/{id}/trash', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Remove a thread from trash */ async untrashThread(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; 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: 'POST', path: '/users/{userId}/threads/{id}/untrash', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Permanently delete a thread */ async deleteThread(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; 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: '/users/{userId}/threads/{id}', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * List draft messages in user's mailbox */ async listDrafts(args = {}) { const pathParams = { userId: args.userId || 'me', }; const queryParams = { ...(args.q !== undefined && { q: args.q }), ...(args.maxResults !== undefined && { maxResults: args.maxResults }), ...(args.pageToken !== undefined && { pageToken: args.pageToken }), ...(args.includeSpamTrash !== undefined && { includeSpamTrash: args.includeSpamTrash }), }; 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/{userId}/drafts', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Get a specific draft by ID */ async getDraft(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; const queryParams = { ...(args.format !== undefined && { format: args.format }), }; 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/{userId}/drafts/{id}', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Create a new draft message */ async createDraft(args = {}) { const pathParams = { userId: args.userId || 'me', }; const queryParams = {}; const bodyParams = { message: args.message, }; // Remove undefined values from pathParams Object.keys(pathParams).forEach(key => { if (pathParams[key] === undefined) { delete pathParams[key]; } }); return this.makeRequest({ method: 'POST', path: '/users/{userId}/drafts', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Update an existing draft */ async updateDraft(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; const queryParams = {}; const bodyParams = { message: args.message, }; // Remove undefined values from pathParams Object.keys(pathParams).forEach(key => { if (pathParams[key] === undefined) { delete pathParams[key]; } }); return this.makeRequest({ method: 'PUT', path: '/users/{userId}/drafts/{id}', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Send an existing draft */ async sendDraft(args = {}) { const pathParams = { userId: args.userId || 'me', }; const queryParams = {}; const bodyParams = { id: args.id, }; // Remove undefined values from pathParams Object.keys(pathParams).forEach(key => { if (pathParams[key] === undefined) { delete pathParams[key]; } }); return this.makeRequest({ method: 'POST', path: '/users/{userId}/drafts/send', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Delete a draft */ async deleteDraft(args = {}) { const pathParams = { userId: args.userId || 'me', id: args.id, }; 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: '/users/{userId}/drafts/{id}', pathParams, queryParams: Object.keys(queryParams).length ? queryParams : undefined, body: Object.keys(bodyParams).length ? bodyParams : undefined }); } /** * Get user's Gmail profile information */ async getProfile(args = {}) { const pathParams = { userId: args.userId || 'me', }; 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: '/users/{userId}/profile', 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-gmail-client.js.map