@coretext-ai/custom-test-google-contacts-a58500d5-8331-4ce9-a140-d204a9fae815
Version:
MCP server with google-contacts integration
2,153 lines • 106 kB
JavaScript
import axios from 'axios';
import { Logger } from '../services/logger.js';
export class GoogleContactsClient {
constructor(config) {
this.config = config;
// Generate unique session ID for this client instance
this.sessionId = `google-contacts-${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: 'google-contacts-mcp-server'
});
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': 'google-contacts-mcp-server/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://people.googleapis.com';
// Handle dynamic domain replacement (e.g., YOUR_DOMAIN placeholder)
if (baseUrl.includes('YOUR_DOMAIN')) {
const domainEnvVar = `GOOGLE_CONTACTS_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_CONTACTS] 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 createContact(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'create_contact',
method: 'POST',
path: '/v1/people:createContact',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/people:createContact';
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["personFields"] !== undefined) {
queryParams["personFields"] = params["personFields"];
extractedParams.push("personFields");
}
if (params["sources"] !== undefined) {
queryParams["sources"] = params["sources"];
extractedParams.push("sources");
}
if (params["names"] !== undefined) {
bodyParams["names"] = params["names"];
extractedParams.push("names");
}
if (params["emailAddresses"] !== undefined) {
bodyParams["emailAddresses"] = params["emailAddresses"];
extractedParams.push("emailAddresses");
}
if (params["phoneNumbers"] !== undefined) {
bodyParams["phoneNumbers"] = params["phoneNumbers"];
extractedParams.push("phoneNumbers");
}
if (params["addresses"] !== undefined) {
bodyParams["addresses"] = params["addresses"];
extractedParams.push("addresses");
}
if (params["organizations"] !== undefined) {
bodyParams["organizations"] = params["organizations"];
extractedParams.push("organizations");
}
if (params["biographies"] !== undefined) {
bodyParams["biographies"] = params["biographies"];
extractedParams.push("biographies");
}
if (params["birthdays"] !== undefined) {
bodyParams["birthdays"] = params["birthdays"];
extractedParams.push("birthdays");
}
if (params["urls"] !== undefined) {
bodyParams["urls"] = params["urls"];
extractedParams.push("urls");
}
// 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.personFields) {
throw new Error(`Missing required parameter: personFields`);
}
const path = this.buildPath('/v1/people:createContact', 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_contact',
method: 'POST',
path: '/v1/people:createContact',
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_contact',
method: 'POST',
path: '/v1/people:createContact',
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_contact: ${error instanceof Error ? error.message : String(error)}`);
}
}
async getPerson(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'get_person',
method: 'GET',
path: '/v1/{resourceName=people/*}',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/{resourceName=people/*}';
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["personFields"] !== undefined) {
queryParams["personFields"] = params["personFields"];
extractedParams.push("personFields");
}
if (params["sources"] !== undefined) {
queryParams["sources"] = params["sources"];
extractedParams.push("sources");
}
// 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.resourceName) {
throw new Error(`Missing required parameter: resourceName`);
}
if (!params.personFields) {
throw new Error(`Missing required parameter: personFields`);
}
const path = this.buildPath('/v1/{resourceName=people/*}', 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_person',
method: 'GET',
path: '/v1/{resourceName=people/*}',
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_person',
method: 'GET',
path: '/v1/{resourceName=people/*}',
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_person: ${error instanceof Error ? error.message : String(error)}`);
}
}
async updateContact(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'update_contact',
method: 'PATCH',
path: '/v1/{person.resourceName=people/*}:updateContact',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/{person.resourceName=people/*}:updateContact';
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["updatePersonFields"] !== undefined) {
queryParams["updatePersonFields"] = params["updatePersonFields"];
extractedParams.push("updatePersonFields");
}
if (params["personFields"] !== undefined) {
queryParams["personFields"] = params["personFields"];
extractedParams.push("personFields");
}
if (params["sources"] !== undefined) {
queryParams["sources"] = params["sources"];
extractedParams.push("sources");
}
if (params["names"] !== undefined) {
bodyParams["names"] = params["names"];
extractedParams.push("names");
}
if (params["emailAddresses"] !== undefined) {
bodyParams["emailAddresses"] = params["emailAddresses"];
extractedParams.push("emailAddresses");
}
if (params["phoneNumbers"] !== undefined) {
bodyParams["phoneNumbers"] = params["phoneNumbers"];
extractedParams.push("phoneNumbers");
}
if (params["addresses"] !== undefined) {
bodyParams["addresses"] = params["addresses"];
extractedParams.push("addresses");
}
if (params["organizations"] !== undefined) {
bodyParams["organizations"] = params["organizations"];
extractedParams.push("organizations");
}
if (params["biographies"] !== undefined) {
bodyParams["biographies"] = params["biographies"];
extractedParams.push("biographies");
}
if (params["birthdays"] !== undefined) {
bodyParams["birthdays"] = params["birthdays"];
extractedParams.push("birthdays");
}
if (params["urls"] !== undefined) {
bodyParams["urls"] = params["urls"];
extractedParams.push("urls");
}
if (params["etag"] !== undefined) {
bodyParams["etag"] = params["etag"];
extractedParams.push("etag");
}
// 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["person.resourceName"]) {
throw new Error(`Missing required parameter: person.resourceName`);
}
if (!params.updatePersonFields) {
throw new Error(`Missing required parameter: updatePersonFields`);
}
if (!params.etag) {
throw new Error(`Missing required parameter: etag`);
}
const path = this.buildPath('/v1/{person.resourceName=people/*}:updateContact', pathParams);
// Use authenticated request for OAuth (both ConstructionWire and standard OAuth)
const response = await this.makeAuthenticatedRequest({ method: 'PATCH', 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_contact',
method: 'PATCH',
path: '/v1/{person.resourceName=people/*}:updateContact',
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_contact',
method: 'PATCH',
path: '/v1/{person.resourceName=people/*}:updateContact',
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_contact: ${error instanceof Error ? error.message : String(error)}`);
}
}
async deleteContact(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'delete_contact',
method: 'DELETE',
path: '/v1/{resourceName=people/*}:deleteContact',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/{resourceName=people/*}:deleteContact';
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.resourceName) {
throw new Error(`Missing required parameter: resourceName`);
}
const path = this.buildPath('/v1/{resourceName=people/*}:deleteContact', 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_contact',
method: 'DELETE',
path: '/v1/{resourceName=people/*}:deleteContact',
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_contact',
method: 'DELETE',
path: '/v1/{resourceName=people/*}:deleteContact',
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_contact: ${error instanceof Error ? error.message : String(error)}`);
}
}
async listConnections(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'list_connections',
method: 'GET',
path: '/v1/people/me/connections',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/people/me/connections';
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["pageSize"] !== undefined) {
queryParams["pageSize"] = params["pageSize"];
extractedParams.push("pageSize");
}
if (params["pageToken"] !== undefined) {
queryParams["pageToken"] = params["pageToken"];
extractedParams.push("pageToken");
}
if (params["personFields"] !== undefined) {
queryParams["personFields"] = params["personFields"];
extractedParams.push("personFields");
}
if (params["sortOrder"] !== undefined) {
queryParams["sortOrder"] = params["sortOrder"];
extractedParams.push("sortOrder");
}
if (params["sources"] !== undefined) {
queryParams["sources"] = params["sources"];
extractedParams.push("sources");
}
if (params["syncToken"] !== undefined) {
queryParams["syncToken"] = params["syncToken"];
extractedParams.push("syncToken");
}
if (params["requestSyncToken"] !== undefined) {
queryParams["requestSyncToken"] = params["requestSyncToken"];
extractedParams.push("requestSyncToken");
}
// 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.personFields) {
throw new Error(`Missing required parameter: personFields`);
}
const path = this.buildPath('/v1/people/me/connections', 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_connections',
method: 'GET',
path: '/v1/people/me/connections',
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_connections',
method: 'GET',
path: '/v1/people/me/connections',
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_connections: ${error instanceof Error ? error.message : String(error)}`);
}
}
async searchContacts(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'search_contacts',
method: 'GET',
path: '/v1/people:searchContacts',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/people:searchContacts';
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["query"] !== undefined) {
queryParams["query"] = params["query"];
extractedParams.push("query");
}
if (params["pageSize"] !== undefined) {
queryParams["pageSize"] = params["pageSize"];
extractedParams.push("pageSize");
}
if (params["readMask"] !== undefined) {
queryParams["readMask"] = params["readMask"];
extractedParams.push("readMask");
}
if (params["sources"] !== undefined) {
queryParams["sources"] = params["sources"];
extractedParams.push("sources");
}
// 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.query) {
throw new Error(`Missing required parameter: query`);
}
const path = this.buildPath('/v1/people:searchContacts', 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: 'search_contacts',
method: 'GET',
path: '/v1/people:searchContacts',
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: 'search_contacts',
method: 'GET',
path: '/v1/people:searchContacts',
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 search_contacts: ${error instanceof Error ? error.message : String(error)}`);
}
}
async batchGetPeople(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'batch_get_people',
method: 'GET',
path: '/v1/people:batchGet',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/people:batchGet';
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["resourceNames"] !== undefined) {
queryParams["resourceNames"] = params["resourceNames"];
extractedParams.push("resourceNames");
}
if (params["personFields"] !== undefined) {
queryParams["personFields"] = params["personFields"];
extractedParams.push("personFields");
}
if (params["sources"] !== undefined) {
queryParams["sources"] = params["sources"];
extractedParams.push("sources");
}
// 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.resourceNames) {
throw new Error(`Missing required parameter: resourceNames`);
}
if (!params.personFields) {
throw new Error(`Missing required parameter: personFields`);
}
const path = this.buildPath('/v1/people:batchGet', 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: 'batch_get_people',
method: 'GET',
path: '/v1/people:batchGet',
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: 'batch_get_people',
method: 'GET',
path: '/v1/people:batchGet',
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 batch_get_people: ${error instanceof Error ? error.message : String(error)}`);
}
}
async listContactGroups(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'list_contact_groups',
method: 'GET',
path: '/v1/contactGroups',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/contactGroups';
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["pageSize"] !== undefined) {
queryParams["pageSize"] = params["pageSize"];
extractedParams.push("pageSize");
}
if (params["pageToken"] !== undefined) {
queryParams["pageToken"] = params["pageToken"];
extractedParams.push("pageToken");
}
if (params["groupFields"] !== undefined) {
queryParams["groupFields"] = params["groupFields"];
extractedParams.push("groupFields");
}
if (params["syncToken"] !== undefined) {
queryParams["syncToken"] = params["syncToken"];
extractedParams.push("syncToken");
}
// 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('/v1/contactGroups', 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_contact_groups',
method: 'GET',
path: '/v1/contactGroups',
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_contact_groups',
method: 'GET',
path: '/v1/contactGroups',
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_contact_groups: ${error instanceof Error ? error.message : String(error)}`);
}
}
async createContactGroup(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'create_contact_group',
method: 'POST',
path: '/v1/contactGroups',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/contactGroups';
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["contactGroup"] !== undefined) {
bodyParams["contactGroup"] = params["contactGroup"];
extractedParams.push("contactGroup");
}
if (params["readGroupFields"] !== undefined) {
queryParams["readGroupFields"] = params["readGroupFields"];
extractedParams.push("readGroupFields");
}
// 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.contactGroup) {
throw new Error(`Missing required parameter: contactGroup`);
}
const path = this.buildPath('/v1/contactGroups', 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_contact_group',
method: 'POST',
path: '/v1/contactGroups',
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_contact_group',
method: 'POST',
path: '/v1/contactGroups',
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_contact_group: ${error instanceof Error ? error.message : String(error)}`);
}
}
async getContactGroup(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'get_contact_group',
method: 'GET',
path: '/v1/{resourceName=contactGroups/*}',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/{resourceName=contactGroups/*}';
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["maxMembers"] !== undefined) {
queryParams["maxMembers"] = params["maxMembers"];
extractedParams.push("maxMembers");
}
if (params["groupFields"] !== undefined) {
queryParams["groupFields"] = params["groupFields"];
extractedParams.push("groupFields");
}
// 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.resourceName) {
throw new Error(`Missing required parameter: resourceName`);
}
const path = this.buildPath('/v1/{resourceName=contactGroups/*}', 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_contact_group',
method: 'GET',
path: '/v1/{resourceName=contactGroups/*}',
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_contact_group',
method: 'GET',
path: '/v1/{resourceName=contactGroups/*}',
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_contact_group: ${error instanceof Error ? error.message : String(error)}`);
}
}
async updateContactGroup(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'update_contact_group',
method: 'PUT',
path: '/v1/{resourceName=contactGroups/*}',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/{resourceName=contactGroups/*}';
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["contactGroup"] !== undefined) {
bodyParams["contactGroup"] = params["contactGroup"];
extractedParams.push("contactGroup");
}
if (params["readGroupFields"] !== undefined) {
queryParams["readGroupFields"] = params["readGroupFields"];
extractedParams.push("readGroupFields");
}
// 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.resourceName) {
throw new Error(`Missing required parameter: resourceName`);
}
if (!params.contactGroup) {
throw new Error(`Missing required parameter: contactGroup`);
}
const path = this.buildPath('/v1/{resourceName=contactGroups/*}', 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_contact_group',
method: 'PUT',
path: '/v1/{resourceName=contactGroups/*}',
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_contact_group',
method: 'PUT',
path: '/v1/{resourceName=contactGroups/*}',
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_contact_group: ${error instanceof Error ? error.message : String(error)}`);
}
}
async deleteContactGroup(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'delete_contact_group',
method: 'DELETE',
path: '/v1/{resourceName=contactGroups/*}',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/{resourceName=contactGroups/*}';
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["deleteContacts"] !== undefined) {
queryParams["deleteContacts"] = params["deleteContacts"];
extractedParams.push("deleteContacts");
}
// 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.resourceName) {
throw new Error(`Missing required parameter: resourceName`);
}
const path = this.buildPath('/v1/{resourceName=contactGroups/*}', 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_contact_group',
method: 'DELETE',
path: '/v1/{resourceName=contactGroups/*}',
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_contact_group',
method: 'DELETE',
path: '/v1/{resourceName=contactGroups/*}',
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_contact_group: ${error instanceof Error ? error.message : String(error)}`);
}
}
async modifyContactGroupMembers(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'modify_contact_group_members',
method: 'POST',
path: '/v1/{resourceName=contactGroups/*}/members:modify',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/{resourceName=contactGroups/*}/members: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["resourceNamesToAdd"] !== undefined) {
bodyParams["resourceNamesToAdd"] = params["resourceNamesToAdd"];
extractedParams.push("resourceNamesToAdd");
}
if (params["resourceNamesToRemove"] !== undefined) {
bodyParams["resourceNamesToRemove"] = params["resourceNamesToRemove"];
extractedParams.push("resourceNamesToRemove");
}
// 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.resourceName) {
throw new Error(`Missing required parameter: resourceName`);
}
const path = this.buildPath('/v1/{resourceName=contactGroups/*}/members: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_contact_group_members',
method: 'POST',
path: '/v1/{resourceName=contactGroups/*}/members: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_contact_group_members',
method: 'POST',
path: '/v1/{resourceName=contactGroups/*}/members: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_contact_group_members: ${error instanceof Error ? error.message : String(error)}`);
}
}
async listDirectoryPeople(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'list_directory_people',
method: 'GET',
path: '/v1/people:listDirectoryPeople',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/people:listDirectoryPeople';
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["pageSize"] !== undefined) {
queryParams["pageSize"] = params["pageSize"];
extractedParams.push("pageSize");
}
if (params["pageToken"] !== undefined) {
queryParams["pageToken"] = params["pageToken"];
extractedParams.push("pageToken");
}
if (params["readMask"] !== undefined) {
queryParams["readMask"] = params["readMask"];
extractedParams.push("readMask");
}
if (params["sources"] !== undefined) {
// Handle array parameters - convert to comma-separated string for Google APIs
queryParams["sources"] = Array.isArray(params["sources"])
? params["sources"].join(',')
: params["sources"];
extractedParams.push("sources");
}
if (params["mergeSources"] !== undefined) {
// Handle array parameters - convert to comma-separated string for Google APIs
queryParams["mergeSources"] = Array.isArray(params["mergeSources"])
? params["mergeSources"].join(',')
: params["mergeSources"];
extractedParams.push("mergeSources");
}
if (params["syncToken"] !== undefined) {
queryParams["syncToken"] = params["syncToken"];
extractedParams.push("syncToken");
}
if (params["requestSyncToken"] !== undefined) {
queryParams["requestSyncToken"] = params["requestSyncToken"];
extractedParams.push("requestSyncToken");
}
// 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.readMask) {
throw new Error(`Missing required parameter: readMask`);
}
if (!params.sources) {
throw new Error(`Missing required parameter: sources`);
}
const path = this.buildPath('/v1/people:listDirectoryPeople', 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_directory_people',
method: 'GET',
path: '/v1/people:listDirectoryPeople',
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_directory_people',
method: 'GET',
path: '/v1/people:listDirectoryPeople',
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_directory_people: ${error instanceof Error ? error.message : String(error)}`);
}
}
async searchDirectoryPeople(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'search_directory_people',
method: 'GET',
path: '/v1/people:searchDirectoryPeople',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/people:searchDirectoryPeople';
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["query"] !== undefined) {
queryParams["query"] = params["query"];
extractedParams.push("query");
}
if (params["pageSize"] !== undefined) {
queryParams["pageSize"] = params["pageSize"];
extractedParams.push("pageSize");
}
if (params["pageToken"] !== undefined) {
queryParams["pageToken"] = params["pageToken"];
extractedParams.push("pageToken");
}
if (params["readMask"] !== undefined) {
queryParams["readMask"] = params["readMask"];
extractedParams.push("readMask");
}
if (params["sources"] !== undefined) {
// Handle array parameters - convert to comma-separated string for Google APIs
queryParams["sources"] = Array.isArray(params["sources"])
? params["sources"].join(',')
: params["sources"];
extractedParams.push("sources");
}
if (params["mergeSources"] !== undefined) {
// Handle array parameters - convert to comma-separated string for Google APIs
queryParams["mergeSources"] = Array.isArray(params["mergeSources"])
? params["mergeSources"].join(',')
: params["mergeSources"];
extractedParams.push("mergeSources");
}
// 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.query) {
throw new Error(`Missing required parameter: query`);
}
if (!params.readMask) {
throw new Error(`Missing required parameter: readMask`);
}
if (!params.sources) {
throw new Error(`Missing required parameter: sources`);
}
const path = this.buildPath('/v1/people:searchDirectoryPeople', 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: 'search_directory_people',
method: 'GET',
path: '/v1/people:searchDirectoryPeople',
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: 'search_directory_people',
method: 'GET',
path: '/v1/people:searchDirectoryPeople',
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 search_directory_people: ${error instanceof Error ? error.message : String(error)}`);
}
}
async listOtherContacts(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'list_other_contacts',
method: 'GET',
path: '/v1/otherContacts',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/otherContacts';
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["pageSize"] !== undefined) {
queryParams["pageSize"] = params["pageSize"];
extractedParams.push("pageSize");
}
if (params["pageToken"] !== undefined) {
queryParams["pageToken"] = params["pageToken"];
extractedParams.push("pageToken");
}
if (params["readMask"] !== undefined) {
queryParams["readMask"] = params["readMask"];
extractedParams.push("readMask");
}
if (params["sources"] !== undefined) {
queryParams["sources"] = params["sources"];
extractedParams.push("sources");
}
if (params["syncToken"] !== undefined) {
queryParams["syncToken"] = params["syncToken"];
extractedParams.push("syncToken");
}
if (params["requestSyncToken"] !== undefined) {
queryParams["requestSyncToken"] = params["requestSyncToken"];
extractedParams.push("requestSyncToken");
}
// 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.readMask) {
throw new Error(`Missing required parameter: readMask`);
}
const path = this.buildPath('/v1/otherContacts', 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_other_contacts',
method: 'GET',
path: '/v1/otherContacts',
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_other_contacts',
method: 'GET',
path: '/v1/otherContacts',
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_other_contacts: ${error instanceof Error ? error.message : String(error)}`);
}
}
async searchOtherContacts(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'search_other_contacts',
method: 'GET',
path: '/v1/otherContacts:search',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/otherContacts:search';
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["query"] !== undefined) {
queryParams["query"] = params["query"];
extractedParams.push("query");
}
if (params["pageSize"] !== undefined) {
queryParams["pageSize"] = params["pageSize"];
extractedParams.push("pageSize");
}
if (params["readMask"] !== undefined) {
queryParams["readMask"] = params["readMask"];
extractedParams.push("readMask");
}
// 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.query) {
throw new Error(`Missing required parameter: query`);
}
const path = this.buildPath('/v1/otherContacts:search', 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: 'search_other_contacts',
method: 'GET',
path: '/v1/otherContacts:search',
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: 'search_other_contacts',
method: 'GET',
path: '/v1/otherContacts:search',
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 search_other_contacts: ${error instanceof Error ? error.message : String(error)}`);
}
}
async copyOtherContact(params) {
const startTime = Date.now();
this.logger.info('ENDPOINT_START', 'Endpoint execution started', {
endpoint: 'copy_other_contact',
method: 'POST',
path: '/v1/{resourceName=otherContacts/*}:copyOtherContactToMyContactsGroup',
paramCount: Object.keys(params || {}).length,
paramKeys: Object.keys(params || {})
});
try {
// Extract and separate parameters by location: path, query, body
const pathTemplate = '/v1/{resourceName=otherContacts/*}:copyOtherContactToMyContactsGroup';
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["copyMask"] !== undefined) {
bodyParams["copyMask"] = params["copyMask"];
extractedParams.push("copyMask");
}
if (params["sources"] !== undefined) {
bodyParams["sources"] = params["sources"];
extractedParams.push("sources");
}
// 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.resourceName) {
throw new Error(`Missing required parameter: resourceName`);
}
if (!params.copyMask) {
throw new Error(`Missing required parameter: copyMask`);
}
const path = this.buildPath('/v1/{resourceName=otherContacts/*}:copyOtherContactToMyContactsGroup', 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: 'copy_other_contact',
method: 'POST',
path: '/v1/{resourceName=otherContacts/*}:copyOtherContactToMyContactsGroup',
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: 'copy_other_contact',
method: 'POST',
path: '/v1/{resourceName=otherContacts/*}:copyOtherContactToMyContactsGroup',
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 copy_other_contact: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
//# sourceMappingURL=google-contacts-client.js.map