@coretext-ai/public-gsuite-mcp-d495ced0-92b0-4d0e-ad89-f27c9e31f4f9
Version:
MCP server with full GSuite integrations
2,568 lines • 126 kB
JavaScript
import axios from 'axios';
import { Logger } from '../services/logger.js';
export class GoogleGmailClient {
constructor(config) {
this.config = config;
// Generate unique session ID for this client instance
this.sessionId = `google-gmail-${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://gmail.googleapis.com/gmail/v1';
// Handle dynamic domain replacement (e.g., YOUR_DOMAIN placeholder)
if (baseUrl.includes('YOUR_DOMAIN')) {
const domainEnvVar = `GOOGLE_GMAIL_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_GMAIL] 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 sendMessage(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'send_message',
method: 'POST',
path: '/users/{userId}/messages/send',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/messages/send';
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["raw"] !== undefined) {
bodyParams["raw"] = params["raw"];
extractedParams.push("raw");
}
// 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.raw) {
throw new Error(`Missing required parameter: raw`);
}
const path = this.buildPath('/users/{userId}/messages/send', 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: 'send_message',
method: 'POST',
path: '/users/{userId}/messages/send',
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: 'send_message',
method: 'POST',
path: '/users/{userId}/messages/send',
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 send_message: ${error instanceof Error ? error.message : String(error)}`);
}
}
async getMessage(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'get_message',
method: 'GET',
path: '/users/{userId}/messages/{id}',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/messages/{id}';
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["format"] !== undefined) {
queryParams["format"] = params["format"];
extractedParams.push("format");
}
if (params["metadataHeaders"] !== undefined) {
queryParams["metadataHeaders"] = params["metadataHeaders"];
extractedParams.push("metadataHeaders");
}
// 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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/messages/{id}', 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_message',
method: 'GET',
path: '/users/{userId}/messages/{id}',
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_message',
method: 'GET',
path: '/users/{userId}/messages/{id}',
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_message: ${error instanceof Error ? error.message : String(error)}`);
}
}
async listMessages(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'list_messages',
method: 'GET',
path: '/users/{userId}/messages',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/messages';
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["q"] !== undefined) {
queryParams["q"] = params["q"];
extractedParams.push("q");
}
if (params["labelIds"] !== undefined) {
queryParams["labelIds"] = params["labelIds"];
extractedParams.push("labelIds");
}
if (params["maxResults"] !== undefined) {
queryParams["maxResults"] = params["maxResults"];
extractedParams.push("maxResults");
}
if (params["pageToken"] !== undefined) {
queryParams["pageToken"] = params["pageToken"];
extractedParams.push("pageToken");
}
if (params["includeSpamTrash"] !== undefined) {
queryParams["includeSpamTrash"] = params["includeSpamTrash"];
extractedParams.push("includeSpamTrash");
}
// 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/{userId}/messages', 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_messages',
method: 'GET',
path: '/users/{userId}/messages',
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_messages',
method: 'GET',
path: '/users/{userId}/messages',
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_messages: ${error instanceof Error ? error.message : String(error)}`);
}
}
async modifyMessage(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'modify_message',
method: 'POST',
path: '/users/{userId}/messages/{id}/modify',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/messages/{id}/modify';
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["addLabelIds"] !== undefined) {
bodyParams["addLabelIds"] = params["addLabelIds"];
extractedParams.push("addLabelIds");
}
if (params["removeLabelIds"] !== undefined) {
bodyParams["removeLabelIds"] = params["removeLabelIds"];
extractedParams.push("removeLabelIds");
}
// 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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/messages/{id}/modify', 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: 'modify_message',
method: 'POST',
path: '/users/{userId}/messages/{id}/modify',
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: 'modify_message',
method: 'POST',
path: '/users/{userId}/messages/{id}/modify',
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 modify_message: ${error instanceof Error ? error.message : String(error)}`);
}
}
async trashMessage(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'trash_message',
method: 'POST',
path: '/users/{userId}/messages/{id}/trash',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/messages/{id}/trash';
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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/messages/{id}/trash', 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: 'trash_message',
method: 'POST',
path: '/users/{userId}/messages/{id}/trash',
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: 'trash_message',
method: 'POST',
path: '/users/{userId}/messages/{id}/trash',
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 trash_message: ${error instanceof Error ? error.message : String(error)}`);
}
}
async untrashMessage(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'untrash_message',
method: 'POST',
path: '/users/{userId}/messages/{id}/untrash',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/messages/{id}/untrash';
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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/messages/{id}/untrash', 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: 'untrash_message',
method: 'POST',
path: '/users/{userId}/messages/{id}/untrash',
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: 'untrash_message',
method: 'POST',
path: '/users/{userId}/messages/{id}/untrash',
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 untrash_message: ${error instanceof Error ? error.message : String(error)}`);
}
}
async deleteMessage(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'delete_message',
method: 'DELETE',
path: '/users/{userId}/messages/{id}',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/messages/{id}';
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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/messages/{id}', 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_message',
method: 'DELETE',
path: '/users/{userId}/messages/{id}',
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_message',
method: 'DELETE',
path: '/users/{userId}/messages/{id}',
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_message: ${error instanceof Error ? error.message : String(error)}`);
}
}
async listLabels(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'list_labels',
method: 'GET',
path: '/users/{userId}/labels',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/labels';
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
const path = this.buildPath('/users/{userId}/labels', 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_labels',
method: 'GET',
path: '/users/{userId}/labels',
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_labels',
method: 'GET',
path: '/users/{userId}/labels',
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_labels: ${error instanceof Error ? error.message : String(error)}`);
}
}
async getLabel(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'get_label',
method: 'GET',
path: '/users/{userId}/labels/{id}',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/labels/{id}';
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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/labels/{id}', 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_label',
method: 'GET',
path: '/users/{userId}/labels/{id}',
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_label',
method: 'GET',
path: '/users/{userId}/labels/{id}',
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_label: ${error instanceof Error ? error.message : String(error)}`);
}
}
async createLabel(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'create_label',
method: 'POST',
path: '/users/{userId}/labels',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/labels';
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["name"] !== undefined) {
bodyParams["name"] = params["name"];
extractedParams.push("name");
}
if (params["labelListVisibility"] !== undefined) {
bodyParams["labelListVisibility"] = params["labelListVisibility"];
extractedParams.push("labelListVisibility");
}
if (params["messageListVisibility"] !== undefined) {
bodyParams["messageListVisibility"] = params["messageListVisibility"];
extractedParams.push("messageListVisibility");
}
if (params["color"] !== undefined) {
bodyParams["color"] = params["color"];
extractedParams.push("color");
}
// 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.name) {
throw new Error(`Missing required parameter: name`);
}
const path = this.buildPath('/users/{userId}/labels', 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_label',
method: 'POST',
path: '/users/{userId}/labels',
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_label',
method: 'POST',
path: '/users/{userId}/labels',
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_label: ${error instanceof Error ? error.message : String(error)}`);
}
}
async updateLabel(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'update_label',
method: 'PUT',
path: '/users/{userId}/labels/{id}',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/labels/{id}';
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["name"] !== undefined) {
bodyParams["name"] = params["name"];
extractedParams.push("name");
}
if (params["labelListVisibility"] !== undefined) {
bodyParams["labelListVisibility"] = params["labelListVisibility"];
extractedParams.push("labelListVisibility");
}
if (params["messageListVisibility"] !== undefined) {
bodyParams["messageListVisibility"] = params["messageListVisibility"];
extractedParams.push("messageListVisibility");
}
if (params["color"] !== undefined) {
bodyParams["color"] = params["color"];
extractedParams.push("color");
}
// 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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/labels/{id}', 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_label',
method: 'PUT',
path: '/users/{userId}/labels/{id}',
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_label',
method: 'PUT',
path: '/users/{userId}/labels/{id}',
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_label: ${error instanceof Error ? error.message : String(error)}`);
}
}
async deleteLabel(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'delete_label',
method: 'DELETE',
path: '/users/{userId}/labels/{id}',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/labels/{id}';
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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/labels/{id}', 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_label',
method: 'DELETE',
path: '/users/{userId}/labels/{id}',
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_label',
method: 'DELETE',
path: '/users/{userId}/labels/{id}',
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_label: ${error instanceof Error ? error.message : String(error)}`);
}
}
async listThreads(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'list_threads',
method: 'GET',
path: '/users/{userId}/threads',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/threads';
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["q"] !== undefined) {
queryParams["q"] = params["q"];
extractedParams.push("q");
}
if (params["labelIds"] !== undefined) {
queryParams["labelIds"] = params["labelIds"];
extractedParams.push("labelIds");
}
if (params["maxResults"] !== undefined) {
queryParams["maxResults"] = params["maxResults"];
extractedParams.push("maxResults");
}
if (params["pageToken"] !== undefined) {
queryParams["pageToken"] = params["pageToken"];
extractedParams.push("pageToken");
}
if (params["includeSpamTrash"] !== undefined) {
queryParams["includeSpamTrash"] = params["includeSpamTrash"];
extractedParams.push("includeSpamTrash");
}
// 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/{userId}/threads', 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_threads',
method: 'GET',
path: '/users/{userId}/threads',
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_threads',
method: 'GET',
path: '/users/{userId}/threads',
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_threads: ${error instanceof Error ? error.message : String(error)}`);
}
}
async getThread(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'get_thread',
method: 'GET',
path: '/users/{userId}/threads/{id}',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/threads/{id}';
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["format"] !== undefined) {
queryParams["format"] = params["format"];
extractedParams.push("format");
}
if (params["metadataHeaders"] !== undefined) {
queryParams["metadataHeaders"] = params["metadataHeaders"];
extractedParams.push("metadataHeaders");
}
// 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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/threads/{id}', 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_thread',
method: 'GET',
path: '/users/{userId}/threads/{id}',
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_thread',
method: 'GET',
path: '/users/{userId}/threads/{id}',
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_thread: ${error instanceof Error ? error.message : String(error)}`);
}
}
async modifyThread(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'modify_thread',
method: 'POST',
path: '/users/{userId}/threads/{id}/modify',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/threads/{id}/modify';
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["addLabelIds"] !== undefined) {
bodyParams["addLabelIds"] = params["addLabelIds"];
extractedParams.push("addLabelIds");
}
if (params["removeLabelIds"] !== undefined) {
bodyParams["removeLabelIds"] = params["removeLabelIds"];
extractedParams.push("removeLabelIds");
}
// 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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/threads/{id}/modify', 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: 'modify_thread',
method: 'POST',
path: '/users/{userId}/threads/{id}/modify',
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: 'modify_thread',
method: 'POST',
path: '/users/{userId}/threads/{id}/modify',
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 modify_thread: ${error instanceof Error ? error.message : String(error)}`);
}
}
async trashThread(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'trash_thread',
method: 'POST',
path: '/users/{userId}/threads/{id}/trash',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/threads/{id}/trash';
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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/threads/{id}/trash', 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: 'trash_thread',
method: 'POST',
path: '/users/{userId}/threads/{id}/trash',
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: 'trash_thread',
method: 'POST',
path: '/users/{userId}/threads/{id}/trash',
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 trash_thread: ${error instanceof Error ? error.message : String(error)}`);
}
}
async untrashThread(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'untrash_thread',
method: 'POST',
path: '/users/{userId}/threads/{id}/untrash',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/threads/{id}/untrash';
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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/threads/{id}/untrash', 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: 'untrash_thread',
method: 'POST',
path: '/users/{userId}/threads/{id}/untrash',
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: 'untrash_thread',
method: 'POST',
path: '/users/{userId}/threads/{id}/untrash',
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 untrash_thread: ${error instanceof Error ? error.message : String(error)}`);
}
}
async deleteThread(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'delete_thread',
method: 'DELETE',
path: '/users/{userId}/threads/{id}',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/threads/{id}';
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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/threads/{id}', 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_thread',
method: 'DELETE',
path: '/users/{userId}/threads/{id}',
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_thread',
method: 'DELETE',
path: '/users/{userId}/threads/{id}',
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_thread: ${error instanceof Error ? error.message : String(error)}`);
}
}
async listDrafts(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'list_drafts',
method: 'GET',
path: '/users/{userId}/drafts',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/drafts';
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["q"] !== undefined) {
queryParams["q"] = params["q"];
extractedParams.push("q");
}
if (params["maxResults"] !== undefined) {
queryParams["maxResults"] = params["maxResults"];
extractedParams.push("maxResults");
}
if (params["pageToken"] !== undefined) {
queryParams["pageToken"] = params["pageToken"];
extractedParams.push("pageToken");
}
if (params["includeSpamTrash"] !== undefined) {
queryParams["includeSpamTrash"] = params["includeSpamTrash"];
extractedParams.push("includeSpamTrash");
}
// 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/{userId}/drafts', 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_drafts',
method: 'GET',
path: '/users/{userId}/drafts',
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_drafts',
method: 'GET',
path: '/users/{userId}/drafts',
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_drafts: ${error instanceof Error ? error.message : String(error)}`);
}
}
async getDraft(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'get_draft',
method: 'GET',
path: '/users/{userId}/drafts/{id}',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/drafts/{id}';
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["format"] !== undefined) {
queryParams["format"] = params["format"];
extractedParams.push("format");
}
// 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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/drafts/{id}', 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_draft',
method: 'GET',
path: '/users/{userId}/drafts/{id}',
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_draft',
method: 'GET',
path: '/users/{userId}/drafts/{id}',
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_draft: ${error instanceof Error ? error.message : String(error)}`);
}
}
async createDraft(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'create_draft',
method: 'POST',
path: '/users/{userId}/drafts',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/drafts';
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["message"] !== undefined) {
bodyParams["message"] = params["message"];
extractedParams.push("message");
}
// 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.message) {
throw new Error(`Missing required parameter: message`);
}
const path = this.buildPath('/users/{userId}/drafts', 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_draft',
method: 'POST',
path: '/users/{userId}/drafts',
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_draft',
method: 'POST',
path: '/users/{userId}/drafts',
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_draft: ${error instanceof Error ? error.message : String(error)}`);
}
}
async updateDraft(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'update_draft',
method: 'PUT',
path: '/users/{userId}/drafts/{id}',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/drafts/{id}';
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["message"] !== undefined) {
bodyParams["message"] = params["message"];
extractedParams.push("message");
}
// 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.id) {
throw new Error(`Missing required parameter: id`);
}
if (!params.message) {
throw new Error(`Missing required parameter: message`);
}
const path = this.buildPath('/users/{userId}/drafts/{id}', 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_draft',
method: 'PUT',
path: '/users/{userId}/drafts/{id}',
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_draft',
method: 'PUT',
path: '/users/{userId}/drafts/{id}',
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_draft: ${error instanceof Error ? error.message : String(error)}`);
}
}
async sendDraft(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'send_draft',
method: 'POST',
path: '/users/{userId}/drafts/send',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/drafts/send';
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["id"] !== undefined) {
bodyParams["id"] = params["id"];
extractedParams.push("id");
}
// 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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/drafts/send', 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: 'send_draft',
method: 'POST',
path: '/users/{userId}/drafts/send',
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: 'send_draft',
method: 'POST',
path: '/users/{userId}/drafts/send',
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 send_draft: ${error instanceof Error ? error.message : String(error)}`);
}
}
async deleteDraft(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'delete_draft',
method: 'DELETE',
path: '/users/{userId}/drafts/{id}',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/drafts/{id}';
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.id) {
throw new Error(`Missing required parameter: id`);
}
const path = this.buildPath('/users/{userId}/drafts/{id}', 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_draft',
method: 'DELETE',
path: '/users/{userId}/drafts/{id}',
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_draft',
method: 'DELETE',
path: '/users/{userId}/drafts/{id}',
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_draft: ${error instanceof Error ? error.message : String(error)}`);
}
}
async getProfile(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'get_profile',
method: 'GET',
path: '/users/{userId}/profile',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/users/{userId}/profile';
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
const path = this.buildPath('/users/{userId}/profile', 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_profile',
method: 'GET',
path: '/users/{userId}/profile',
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_profile',
method: 'GET',
path: '/users/{userId}/profile',
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_profile: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
//# sourceMappingURL=google-gmail-client.js.map