@coretext-ai/qa-gsuite-f47ac10b-58cc-4372-a567-0e02b2c3d479
Version:
MCP server with GSuite (Contacts, Drive, Gmail, Calendar) integration
765 lines • 26.8 kB
JavaScript
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