@channel47/google-ads-mcp 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/server/tools/mutate.js +88 -25
package/package.json
CHANGED
package/server/tools/mutate.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { getCustomerClient } from '../auth.js';
|
|
3
|
+
import { formatSuccess, formatError } from '../utils/response-format.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Execute mutation operations using GoogleAdsService.Mutate
|
|
@@ -22,43 +23,105 @@ export async function mutate(params) {
|
|
|
22
23
|
|
|
23
24
|
// Validate required parameters
|
|
24
25
|
if (!customer_id) {
|
|
25
|
-
return
|
|
26
|
-
success: false,
|
|
27
|
-
error: 'customer_id is required (either as parameter or GOOGLE_ADS_CUSTOMER_ID env var)'
|
|
28
|
-
};
|
|
26
|
+
return formatError(new Error('customer_id is required (either as parameter or GOOGLE_ADS_CUSTOMER_ID env var)'));
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
if (!operations || !Array.isArray(operations) || operations.length === 0) {
|
|
32
|
-
return
|
|
33
|
-
success: false,
|
|
34
|
-
error: 'operations array is required and must contain at least one operation'
|
|
35
|
-
};
|
|
30
|
+
return formatError(new Error('operations array is required and must contain at least one operation'));
|
|
36
31
|
}
|
|
37
32
|
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
const customer = getCustomerClient(customer_id);
|
|
34
|
+
let response;
|
|
35
|
+
let partialFailureErrors = [];
|
|
40
36
|
|
|
37
|
+
try {
|
|
41
38
|
// Execute mutation with validation options
|
|
42
|
-
|
|
39
|
+
response = await customer.mutateResources(operations, {
|
|
43
40
|
partialFailure: partial_failure,
|
|
44
41
|
validateOnly: dry_run
|
|
45
42
|
});
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
success: true,
|
|
49
|
-
dry_run,
|
|
50
|
-
results: response,
|
|
51
|
-
message: dry_run
|
|
52
|
-
? 'Validation successful - no changes made'
|
|
53
|
-
: 'Mutations applied successfully',
|
|
54
|
-
operations_count: operations.length
|
|
55
|
-
};
|
|
56
43
|
} catch (error) {
|
|
44
|
+
// The Opteo library throws exceptions with error.errors array for partial failures
|
|
45
|
+
// Extract failure details if available
|
|
46
|
+
if (partial_failure && error.errors && Array.isArray(error.errors)) {
|
|
47
|
+
for (const err of error.errors) {
|
|
48
|
+
const opIndex = err.location?.field_path_elements?.[0]?.index ?? -1;
|
|
49
|
+
partialFailureErrors.push({
|
|
50
|
+
message: err.message || JSON.stringify(err.error_code),
|
|
51
|
+
error_code: err.error_code,
|
|
52
|
+
operation_index: opIndex
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// If not all operations failed, treat as partial success
|
|
57
|
+
if (partialFailureErrors.length < operations.length) {
|
|
58
|
+
response = { mutate_operation_responses: [], partial_failure_error: null };
|
|
59
|
+
} else {
|
|
60
|
+
// All operations failed
|
|
61
|
+
const errorMessages = partialFailureErrors.map(e => e.message).join('; ');
|
|
62
|
+
return formatError(new Error(`All operations failed: ${errorMessages}`));
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
// Not a partial failure - re-throw as regular error
|
|
66
|
+
return formatError(error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Extract results from response
|
|
71
|
+
const results = response.mutate_operation_responses || [];
|
|
72
|
+
|
|
73
|
+
// Check for partial failure errors in response body (alternative structure)
|
|
74
|
+
if (response.partial_failure_error) {
|
|
75
|
+
const errorDetails = response.partial_failure_error.errors
|
|
76
|
+
|| response.partial_failure_error.details
|
|
77
|
+
|| [];
|
|
78
|
+
|
|
79
|
+
for (const error of errorDetails) {
|
|
80
|
+
partialFailureErrors.push({
|
|
81
|
+
message: error.message || error.error_message || JSON.stringify(error),
|
|
82
|
+
error_code: error.error_code || error.code,
|
|
83
|
+
operation_index: error.location?.field_path_elements?.[0]?.index ?? -1
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Calculate success/failure counts
|
|
89
|
+
const failCount = partialFailureErrors.length;
|
|
90
|
+
const successCount = operations.length - failCount;
|
|
91
|
+
|
|
92
|
+
// Extract resource names from nested result structure
|
|
93
|
+
// Results come as: { campaign_result: { resource_name: "..." }, response: "campaign_result" }
|
|
94
|
+
const extractedResults = results.map(r => {
|
|
95
|
+
// Find the result field (campaign_result, ad_group_result, etc.)
|
|
96
|
+
const resultKey = r?.response;
|
|
97
|
+
const resultData = resultKey ? r[resultKey] : r;
|
|
57
98
|
return {
|
|
58
|
-
|
|
59
|
-
dry_run,
|
|
60
|
-
error: error.message,
|
|
61
|
-
error_details: error.errors || null
|
|
99
|
+
resource_name: resultData?.resource_name || null
|
|
62
100
|
};
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Build appropriate message
|
|
104
|
+
let message;
|
|
105
|
+
if (dry_run) {
|
|
106
|
+
message = failCount > 0
|
|
107
|
+
? `Validation completed with ${failCount} error(s) - no changes made`
|
|
108
|
+
: 'Validation successful - no changes made';
|
|
109
|
+
} else {
|
|
110
|
+
message = failCount > 0
|
|
111
|
+
? `Mutations completed: ${successCount} succeeded, ${failCount} failed`
|
|
112
|
+
: 'Mutations applied successfully';
|
|
63
113
|
}
|
|
114
|
+
|
|
115
|
+
return formatSuccess({
|
|
116
|
+
summary: `${message} (${operations.length} operation${operations.length !== 1 ? 's' : ''})`,
|
|
117
|
+
data: extractedResults,
|
|
118
|
+
metadata: {
|
|
119
|
+
dry_run,
|
|
120
|
+
operations_count: operations.length,
|
|
121
|
+
success_count: successCount,
|
|
122
|
+
failure_count: failCount,
|
|
123
|
+
customer_id,
|
|
124
|
+
...(partialFailureErrors.length > 0 && { errors: partialFailureErrors })
|
|
125
|
+
}
|
|
126
|
+
});
|
|
64
127
|
}
|