@channel47/google-ads-mcp 1.0.2 → 1.0.4
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
CHANGED
package/server/tools/mutate.js
CHANGED
|
@@ -60,10 +60,14 @@ export async function mutate(params) {
|
|
|
60
60
|
if (partial_failure && error.errors && Array.isArray(error.errors)) {
|
|
61
61
|
for (const err of error.errors) {
|
|
62
62
|
const opIndex = err.location?.field_path_elements?.[0]?.index ?? -1;
|
|
63
|
+
// Extract full field path for debugging (e.g., "operations.create.campaign_bidding_strategy")
|
|
64
|
+
const fieldPath = err.location?.field_path_elements?.map(e => e.field_name).join('.') || null;
|
|
63
65
|
partialFailureErrors.push({
|
|
64
66
|
message: err.message || JSON.stringify(err.error_code),
|
|
65
67
|
error_code: err.error_code,
|
|
66
|
-
operation_index: opIndex
|
|
68
|
+
operation_index: opIndex,
|
|
69
|
+
field_path: fieldPath,
|
|
70
|
+
trigger: err.trigger?.string_value || null
|
|
67
71
|
});
|
|
68
72
|
}
|
|
69
73
|
|
|
@@ -91,10 +95,13 @@ export async function mutate(params) {
|
|
|
91
95
|
|| [];
|
|
92
96
|
|
|
93
97
|
for (const error of errorDetails) {
|
|
98
|
+
const fieldPath = error.location?.field_path_elements?.map(e => e.field_name).join('.') || null;
|
|
94
99
|
partialFailureErrors.push({
|
|
95
100
|
message: error.message || error.error_message || JSON.stringify(error),
|
|
96
101
|
error_code: error.error_code || error.code,
|
|
97
|
-
operation_index: error.location?.field_path_elements?.[0]?.index ?? -1
|
|
102
|
+
operation_index: error.location?.field_path_elements?.[0]?.index ?? -1,
|
|
103
|
+
field_path: fieldPath,
|
|
104
|
+
trigger: error.trigger?.string_value || null
|
|
98
105
|
});
|
|
99
106
|
}
|
|
100
107
|
}
|
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
* Converts standard Google Ads API format to Opteo library format
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Entities that legitimately use resource_name in CREATE operations.
|
|
8
|
+
* These use temporary resource IDs (-1, -2, etc.) for atomic multi-resource creation.
|
|
9
|
+
* See: https://developers.google.com/google-ads/api/docs/mutating/overview
|
|
10
|
+
*/
|
|
11
|
+
const ENTITIES_REQUIRING_RESOURCE_NAME_IN_CREATE = new Set([
|
|
12
|
+
'campaign_budget' // Used for temp IDs when creating budget + campaign atomically
|
|
13
|
+
]);
|
|
14
|
+
|
|
6
15
|
/**
|
|
7
16
|
* Resource name URL path segments to entity type mapping
|
|
8
17
|
* Based on Google Ads API resource name patterns
|
|
@@ -156,12 +165,13 @@ function transformToOpteoFormat(operation, index) {
|
|
|
156
165
|
|
|
157
166
|
if (opType === 'remove') {
|
|
158
167
|
// Remove operations have resource_name as string value
|
|
168
|
+
// The Opteo library expects resource to be the string directly for remove ops
|
|
159
169
|
const resourceName = operation.remove;
|
|
160
170
|
if (typeof resourceName !== 'string') {
|
|
161
171
|
throw new Error(`Operation ${index}: 'remove' value must be a resource_name string`);
|
|
162
172
|
}
|
|
163
173
|
entity = inferEntityFromResourceName(resourceName);
|
|
164
|
-
resource = {
|
|
174
|
+
resource = resourceName; // String, not object - Opteo expects { remove: "resource_name" }
|
|
165
175
|
} else {
|
|
166
176
|
// Create/Update operations have resource as object
|
|
167
177
|
resource = operation[opType];
|
|
@@ -193,6 +203,16 @@ function transformToOpteoFormat(operation, index) {
|
|
|
193
203
|
);
|
|
194
204
|
}
|
|
195
205
|
|
|
206
|
+
// Strip resource_name from CREATE operations (API generates it automatically)
|
|
207
|
+
// Exception: entities that use temp IDs for atomic multi-resource creation
|
|
208
|
+
// See: https://developers.google.com/google-ads/api/docs/campaigns/create-campaigns
|
|
209
|
+
if (opType === 'create' && resource.resource_name) {
|
|
210
|
+
if (!ENTITIES_REQUIRING_RESOURCE_NAME_IN_CREATE.has(entity)) {
|
|
211
|
+
const { resource_name, ...resourceWithoutName } = resource;
|
|
212
|
+
resource = resourceWithoutName;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
196
216
|
return {
|
|
197
217
|
entity,
|
|
198
218
|
operation: opType,
|
|
@@ -214,8 +234,16 @@ export function normalizeOperations(operations) {
|
|
|
214
234
|
const op = operations[i];
|
|
215
235
|
|
|
216
236
|
if (isOpteoFormat(op)) {
|
|
217
|
-
// Already in Opteo format - pass through
|
|
218
|
-
|
|
237
|
+
// Already in Opteo format - pass through, but normalize remove operations
|
|
238
|
+
// For remove ops, ensure resource is the string, not an object
|
|
239
|
+
if (op.operation === 'remove' && op.resource && typeof op.resource === 'object') {
|
|
240
|
+
normalizedOps.push({
|
|
241
|
+
...op,
|
|
242
|
+
resource: op.resource.resource_name || op.resource
|
|
243
|
+
});
|
|
244
|
+
} else {
|
|
245
|
+
normalizedOps.push(op);
|
|
246
|
+
}
|
|
219
247
|
} else {
|
|
220
248
|
// Transform from standard format
|
|
221
249
|
const transformed = transformToOpteoFormat(op, i);
|