@cesteral/gads-mcp 1.0.0

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.
Files changed (251) hide show
  1. package/LICENSE.md +201 -0
  2. package/README.md +262 -0
  3. package/dist/auth/gads-auth-adapter.d.ts +31 -0
  4. package/dist/auth/gads-auth-adapter.d.ts.map +1 -0
  5. package/dist/auth/gads-auth-adapter.js +70 -0
  6. package/dist/auth/gads-auth-adapter.js.map +1 -0
  7. package/dist/auth/gads-auth-strategy.d.ts +9 -0
  8. package/dist/auth/gads-auth-strategy.d.ts.map +1 -0
  9. package/dist/auth/gads-auth-strategy.js +27 -0
  10. package/dist/auth/gads-auth-strategy.js.map +1 -0
  11. package/dist/config/index.d.ts +100 -0
  12. package/dist/config/index.d.ts.map +1 -0
  13. package/dist/config/index.js +37 -0
  14. package/dist/config/index.js.map +1 -0
  15. package/dist/container/index.d.ts +5 -0
  16. package/dist/container/index.d.ts.map +1 -0
  17. package/dist/container/index.js +14 -0
  18. package/dist/container/index.js.map +1 -0
  19. package/dist/container/registrations/core.d.ts +3 -0
  20. package/dist/container/registrations/core.d.ts.map +1 -0
  21. package/dist/container/registrations/core.js +14 -0
  22. package/dist/container/registrations/core.js.map +1 -0
  23. package/dist/container/registrations/mcp.d.ts +2 -0
  24. package/dist/container/registrations/mcp.d.ts.map +1 -0
  25. package/dist/container/registrations/mcp.js +3 -0
  26. package/dist/container/registrations/mcp.js.map +1 -0
  27. package/dist/container/tokens.d.ts +6 -0
  28. package/dist/container/tokens.d.ts.map +1 -0
  29. package/dist/container/tokens.js +6 -0
  30. package/dist/container/tokens.js.map +1 -0
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +50 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/mcp-server/prompts/definitions/bulk-operations-workflow.prompt.d.ts +4 -0
  36. package/dist/mcp-server/prompts/definitions/bulk-operations-workflow.prompt.d.ts.map +1 -0
  37. package/dist/mcp-server/prompts/definitions/bulk-operations-workflow.prompt.js +230 -0
  38. package/dist/mcp-server/prompts/definitions/bulk-operations-workflow.prompt.js.map +1 -0
  39. package/dist/mcp-server/prompts/definitions/campaign-setup-workflow.prompt.d.ts +4 -0
  40. package/dist/mcp-server/prompts/definitions/campaign-setup-workflow.prompt.d.ts.map +1 -0
  41. package/dist/mcp-server/prompts/definitions/campaign-setup-workflow.prompt.js +237 -0
  42. package/dist/mcp-server/prompts/definitions/campaign-setup-workflow.prompt.js.map +1 -0
  43. package/dist/mcp-server/prompts/definitions/creative-setup-workflow.prompt.d.ts +4 -0
  44. package/dist/mcp-server/prompts/definitions/creative-setup-workflow.prompt.d.ts.map +1 -0
  45. package/dist/mcp-server/prompts/definitions/creative-setup-workflow.prompt.js +154 -0
  46. package/dist/mcp-server/prompts/definitions/creative-setup-workflow.prompt.js.map +1 -0
  47. package/dist/mcp-server/prompts/definitions/cross-platform-campaign-setup.prompt.d.ts +4 -0
  48. package/dist/mcp-server/prompts/definitions/cross-platform-campaign-setup.prompt.d.ts.map +1 -0
  49. package/dist/mcp-server/prompts/definitions/cross-platform-campaign-setup.prompt.js +299 -0
  50. package/dist/mcp-server/prompts/definitions/cross-platform-campaign-setup.prompt.js.map +1 -0
  51. package/dist/mcp-server/prompts/definitions/cross-platform-performance.prompt.d.ts +4 -0
  52. package/dist/mcp-server/prompts/definitions/cross-platform-performance.prompt.d.ts.map +1 -0
  53. package/dist/mcp-server/prompts/definitions/cross-platform-performance.prompt.js +170 -0
  54. package/dist/mcp-server/prompts/definitions/cross-platform-performance.prompt.js.map +1 -0
  55. package/dist/mcp-server/prompts/definitions/entity-duplication-workflow.prompt.d.ts +4 -0
  56. package/dist/mcp-server/prompts/definitions/entity-duplication-workflow.prompt.d.ts.map +1 -0
  57. package/dist/mcp-server/prompts/definitions/entity-duplication-workflow.prompt.js +221 -0
  58. package/dist/mcp-server/prompts/definitions/entity-duplication-workflow.prompt.js.map +1 -0
  59. package/dist/mcp-server/prompts/definitions/entity-update-workflow.prompt.d.ts +4 -0
  60. package/dist/mcp-server/prompts/definitions/entity-update-workflow.prompt.d.ts.map +1 -0
  61. package/dist/mcp-server/prompts/definitions/entity-update-workflow.prompt.js +207 -0
  62. package/dist/mcp-server/prompts/definitions/entity-update-workflow.prompt.js.map +1 -0
  63. package/dist/mcp-server/prompts/definitions/gaql-reporting-workflow.prompt.d.ts +4 -0
  64. package/dist/mcp-server/prompts/definitions/gaql-reporting-workflow.prompt.d.ts.map +1 -0
  65. package/dist/mcp-server/prompts/definitions/gaql-reporting-workflow.prompt.js +182 -0
  66. package/dist/mcp-server/prompts/definitions/gaql-reporting-workflow.prompt.js.map +1 -0
  67. package/dist/mcp-server/prompts/definitions/targeting-discovery-workflow.prompt.d.ts +4 -0
  68. package/dist/mcp-server/prompts/definitions/targeting-discovery-workflow.prompt.d.ts.map +1 -0
  69. package/dist/mcp-server/prompts/definitions/targeting-discovery-workflow.prompt.js +223 -0
  70. package/dist/mcp-server/prompts/definitions/targeting-discovery-workflow.prompt.js.map +1 -0
  71. package/dist/mcp-server/prompts/definitions/tool-schema-exploration.prompt.d.ts +4 -0
  72. package/dist/mcp-server/prompts/definitions/tool-schema-exploration.prompt.d.ts.map +1 -0
  73. package/dist/mcp-server/prompts/definitions/tool-schema-exploration.prompt.js +125 -0
  74. package/dist/mcp-server/prompts/definitions/tool-schema-exploration.prompt.js.map +1 -0
  75. package/dist/mcp-server/prompts/definitions/troubleshoot-entity.prompt.d.ts +4 -0
  76. package/dist/mcp-server/prompts/definitions/troubleshoot-entity.prompt.d.ts.map +1 -0
  77. package/dist/mcp-server/prompts/definitions/troubleshoot-entity.prompt.js +177 -0
  78. package/dist/mcp-server/prompts/definitions/troubleshoot-entity.prompt.js.map +1 -0
  79. package/dist/mcp-server/prompts/index.d.ts +9 -0
  80. package/dist/mcp-server/prompts/index.d.ts.map +1 -0
  81. package/dist/mcp-server/prompts/index.js +97 -0
  82. package/dist/mcp-server/prompts/index.js.map +1 -0
  83. package/dist/mcp-server/resources/definitions/entity-examples.resource.d.ts +4 -0
  84. package/dist/mcp-server/resources/definitions/entity-examples.resource.d.ts.map +1 -0
  85. package/dist/mcp-server/resources/definitions/entity-examples.resource.js +574 -0
  86. package/dist/mcp-server/resources/definitions/entity-examples.resource.js.map +1 -0
  87. package/dist/mcp-server/resources/definitions/entity-hierarchy.resource.d.ts +3 -0
  88. package/dist/mcp-server/resources/definitions/entity-hierarchy.resource.d.ts.map +1 -0
  89. package/dist/mcp-server/resources/definitions/entity-hierarchy.resource.js +124 -0
  90. package/dist/mcp-server/resources/definitions/entity-hierarchy.resource.js.map +1 -0
  91. package/dist/mcp-server/resources/definitions/entity-schemas.resource.d.ts +4 -0
  92. package/dist/mcp-server/resources/definitions/entity-schemas.resource.d.ts.map +1 -0
  93. package/dist/mcp-server/resources/definitions/entity-schemas.resource.js +264 -0
  94. package/dist/mcp-server/resources/definitions/entity-schemas.resource.js.map +1 -0
  95. package/dist/mcp-server/resources/definitions/gaql-reference.resource.d.ts +3 -0
  96. package/dist/mcp-server/resources/definitions/gaql-reference.resource.d.ts.map +1 -0
  97. package/dist/mcp-server/resources/definitions/gaql-reference.resource.js +157 -0
  98. package/dist/mcp-server/resources/definitions/gaql-reference.resource.js.map +1 -0
  99. package/dist/mcp-server/resources/definitions/index.d.ts +8 -0
  100. package/dist/mcp-server/resources/definitions/index.d.ts.map +1 -0
  101. package/dist/mcp-server/resources/definitions/index.js +42 -0
  102. package/dist/mcp-server/resources/definitions/index.js.map +1 -0
  103. package/dist/mcp-server/resources/definitions/insights-reference.resource.d.ts +3 -0
  104. package/dist/mcp-server/resources/definitions/insights-reference.resource.d.ts.map +1 -0
  105. package/dist/mcp-server/resources/definitions/insights-reference.resource.js +78 -0
  106. package/dist/mcp-server/resources/definitions/insights-reference.resource.js.map +1 -0
  107. package/dist/mcp-server/resources/index.d.ts +3 -0
  108. package/dist/mcp-server/resources/index.d.ts.map +1 -0
  109. package/dist/mcp-server/resources/index.js +2 -0
  110. package/dist/mcp-server/resources/index.js.map +1 -0
  111. package/dist/mcp-server/resources/types.d.ts +8 -0
  112. package/dist/mcp-server/resources/types.d.ts.map +1 -0
  113. package/dist/mcp-server/resources/types.js +2 -0
  114. package/dist/mcp-server/resources/types.js.map +1 -0
  115. package/dist/mcp-server/server.d.ts +5 -0
  116. package/dist/mcp-server/server.d.ts.map +1 -0
  117. package/dist/mcp-server/server.js +124 -0
  118. package/dist/mcp-server/server.js.map +1 -0
  119. package/dist/mcp-server/tools/definitions/adjust-bids.tool.d.ts +270 -0
  120. package/dist/mcp-server/tools/definitions/adjust-bids.tool.d.ts.map +1 -0
  121. package/dist/mcp-server/tools/definitions/adjust-bids.tool.js +189 -0
  122. package/dist/mcp-server/tools/definitions/adjust-bids.tool.js.map +1 -0
  123. package/dist/mcp-server/tools/definitions/bulk-create-entities.tool.d.ts +148 -0
  124. package/dist/mcp-server/tools/definitions/bulk-create-entities.tool.d.ts.map +1 -0
  125. package/dist/mcp-server/tools/definitions/bulk-create-entities.tool.js +141 -0
  126. package/dist/mcp-server/tools/definitions/bulk-create-entities.tool.js.map +1 -0
  127. package/dist/mcp-server/tools/definitions/bulk-mutate.tool.d.ts +136 -0
  128. package/dist/mcp-server/tools/definitions/bulk-mutate.tool.d.ts.map +1 -0
  129. package/dist/mcp-server/tools/definitions/bulk-mutate.tool.js +127 -0
  130. package/dist/mcp-server/tools/definitions/bulk-mutate.tool.js.map +1 -0
  131. package/dist/mcp-server/tools/definitions/bulk-update-status.tool.d.ts +185 -0
  132. package/dist/mcp-server/tools/definitions/bulk-update-status.tool.d.ts.map +1 -0
  133. package/dist/mcp-server/tools/definitions/bulk-update-status.tool.js +149 -0
  134. package/dist/mcp-server/tools/definitions/bulk-update-status.tool.js.map +1 -0
  135. package/dist/mcp-server/tools/definitions/create-entity.tool.d.ts +123 -0
  136. package/dist/mcp-server/tools/definitions/create-entity.tool.d.ts.map +1 -0
  137. package/dist/mcp-server/tools/definitions/create-entity.tool.js +98 -0
  138. package/dist/mcp-server/tools/definitions/create-entity.tool.js.map +1 -0
  139. package/dist/mcp-server/tools/definitions/gaql-search.tool.d.ts +197 -0
  140. package/dist/mcp-server/tools/definitions/gaql-search.tool.d.ts.map +1 -0
  141. package/dist/mcp-server/tools/definitions/gaql-search.tool.js +110 -0
  142. package/dist/mcp-server/tools/definitions/gaql-search.tool.js.map +1 -0
  143. package/dist/mcp-server/tools/definitions/get-ad-preview.tool.d.ts +87 -0
  144. package/dist/mcp-server/tools/definitions/get-ad-preview.tool.d.ts.map +1 -0
  145. package/dist/mcp-server/tools/definitions/get-ad-preview.tool.js +91 -0
  146. package/dist/mcp-server/tools/definitions/get-ad-preview.tool.js.map +1 -0
  147. package/dist/mcp-server/tools/definitions/get-entity.tool.d.ts +92 -0
  148. package/dist/mcp-server/tools/definitions/get-entity.tool.d.ts.map +1 -0
  149. package/dist/mcp-server/tools/definitions/get-entity.tool.js +87 -0
  150. package/dist/mcp-server/tools/definitions/get-entity.tool.js.map +1 -0
  151. package/dist/mcp-server/tools/definitions/get-insights.tool.d.ts +382 -0
  152. package/dist/mcp-server/tools/definitions/get-insights.tool.d.ts.map +1 -0
  153. package/dist/mcp-server/tools/definitions/get-insights.tool.js +246 -0
  154. package/dist/mcp-server/tools/definitions/get-insights.tool.js.map +1 -0
  155. package/dist/mcp-server/tools/definitions/get-pacing-status.tool.d.ts +141 -0
  156. package/dist/mcp-server/tools/definitions/get-pacing-status.tool.d.ts.map +1 -0
  157. package/dist/mcp-server/tools/definitions/get-pacing-status.tool.js +163 -0
  158. package/dist/mcp-server/tools/definitions/get-pacing-status.tool.js.map +1 -0
  159. package/dist/mcp-server/tools/definitions/index.d.ts +18 -0
  160. package/dist/mcp-server/tools/definitions/index.d.ts.map +1 -0
  161. package/dist/mcp-server/tools/definitions/index.js +53 -0
  162. package/dist/mcp-server/tools/definitions/index.js.map +1 -0
  163. package/dist/mcp-server/tools/definitions/list-accounts.tool.d.ts +54 -0
  164. package/dist/mcp-server/tools/definitions/list-accounts.tool.d.ts.map +1 -0
  165. package/dist/mcp-server/tools/definitions/list-accounts.tool.js +61 -0
  166. package/dist/mcp-server/tools/definitions/list-accounts.tool.js.map +1 -0
  167. package/dist/mcp-server/tools/definitions/list-entities.tool.d.ts +220 -0
  168. package/dist/mcp-server/tools/definitions/list-entities.tool.d.ts.map +1 -0
  169. package/dist/mcp-server/tools/definitions/list-entities.tool.js +112 -0
  170. package/dist/mcp-server/tools/definitions/list-entities.tool.js.map +1 -0
  171. package/dist/mcp-server/tools/definitions/remove-entity.tool.d.ts +116 -0
  172. package/dist/mcp-server/tools/definitions/remove-entity.tool.d.ts.map +1 -0
  173. package/dist/mcp-server/tools/definitions/remove-entity.tool.js +120 -0
  174. package/dist/mcp-server/tools/definitions/remove-entity.tool.js.map +1 -0
  175. package/dist/mcp-server/tools/definitions/update-entity.tool.d.ts +129 -0
  176. package/dist/mcp-server/tools/definitions/update-entity.tool.d.ts.map +1 -0
  177. package/dist/mcp-server/tools/definitions/update-entity.tool.js +91 -0
  178. package/dist/mcp-server/tools/definitions/update-entity.tool.js.map +1 -0
  179. package/dist/mcp-server/tools/definitions/validate-entity.tool.d.ts +239 -0
  180. package/dist/mcp-server/tools/definitions/validate-entity.tool.d.ts.map +1 -0
  181. package/dist/mcp-server/tools/definitions/validate-entity.tool.js +151 -0
  182. package/dist/mcp-server/tools/definitions/validate-entity.tool.js.map +1 -0
  183. package/dist/mcp-server/tools/index.d.ts +2 -0
  184. package/dist/mcp-server/tools/index.d.ts.map +1 -0
  185. package/dist/mcp-server/tools/index.js +2 -0
  186. package/dist/mcp-server/tools/index.js.map +1 -0
  187. package/dist/mcp-server/tools/utils/computed-metrics.d.ts +8 -0
  188. package/dist/mcp-server/tools/utils/computed-metrics.d.ts.map +1 -0
  189. package/dist/mcp-server/tools/utils/computed-metrics.js +19 -0
  190. package/dist/mcp-server/tools/utils/computed-metrics.js.map +1 -0
  191. package/dist/mcp-server/tools/utils/entity-mapping.d.ts +22 -0
  192. package/dist/mcp-server/tools/utils/entity-mapping.d.ts.map +1 -0
  193. package/dist/mcp-server/tools/utils/entity-mapping.js +99 -0
  194. package/dist/mcp-server/tools/utils/entity-mapping.js.map +1 -0
  195. package/dist/mcp-server/tools/utils/gaql-helpers.d.ts +4 -0
  196. package/dist/mcp-server/tools/utils/gaql-helpers.d.ts.map +1 -0
  197. package/dist/mcp-server/tools/utils/gaql-helpers.js +82 -0
  198. package/dist/mcp-server/tools/utils/gaql-helpers.js.map +1 -0
  199. package/dist/mcp-server/tools/utils/parent-id-validation.d.ts +10 -0
  200. package/dist/mcp-server/tools/utils/parent-id-validation.d.ts.map +1 -0
  201. package/dist/mcp-server/tools/utils/parent-id-validation.js +58 -0
  202. package/dist/mcp-server/tools/utils/parent-id-validation.js.map +1 -0
  203. package/dist/mcp-server/tools/utils/resolve-session.d.ts +4 -0
  204. package/dist/mcp-server/tools/utils/resolve-session.d.ts.map +1 -0
  205. package/dist/mcp-server/tools/utils/resolve-session.js +6 -0
  206. package/dist/mcp-server/tools/utils/resolve-session.js.map +1 -0
  207. package/dist/mcp-server/transports/streamable-http-transport.d.ts +49 -0
  208. package/dist/mcp-server/transports/streamable-http-transport.d.ts.map +1 -0
  209. package/dist/mcp-server/transports/streamable-http-transport.js +84 -0
  210. package/dist/mcp-server/transports/streamable-http-transport.js.map +1 -0
  211. package/dist/services/gads/gads-http-client.d.ts +13 -0
  212. package/dist/services/gads/gads-http-client.d.ts.map +1 -0
  213. package/dist/services/gads/gads-http-client.js +113 -0
  214. package/dist/services/gads/gads-http-client.js.map +1 -0
  215. package/dist/services/gads/gads-service.d.ts +60 -0
  216. package/dist/services/gads/gads-service.d.ts.map +1 -0
  217. package/dist/services/gads/gads-service.js +363 -0
  218. package/dist/services/gads/gads-service.js.map +1 -0
  219. package/dist/services/gads/types.d.ts +211 -0
  220. package/dist/services/gads/types.d.ts.map +1 -0
  221. package/dist/services/gads/types.js +16 -0
  222. package/dist/services/gads/types.js.map +1 -0
  223. package/dist/services/session-services.d.ts +17 -0
  224. package/dist/services/session-services.d.ts.map +1 -0
  225. package/dist/services/session-services.js +14 -0
  226. package/dist/services/session-services.js.map +1 -0
  227. package/dist/types-global/mcp.d.ts +2 -0
  228. package/dist/types-global/mcp.d.ts.map +1 -0
  229. package/dist/types-global/mcp.js +2 -0
  230. package/dist/types-global/mcp.js.map +1 -0
  231. package/dist/utils/errors/index.d.ts +2 -0
  232. package/dist/utils/errors/index.d.ts.map +1 -0
  233. package/dist/utils/errors/index.js +2 -0
  234. package/dist/utils/errors/index.js.map +1 -0
  235. package/dist/utils/platform.d.ts +3 -0
  236. package/dist/utils/platform.d.ts.map +1 -0
  237. package/dist/utils/platform.js +5 -0
  238. package/dist/utils/platform.js.map +1 -0
  239. package/dist/utils/security/rate-limiter.d.ts +3 -0
  240. package/dist/utils/security/rate-limiter.d.ts.map +1 -0
  241. package/dist/utils/security/rate-limiter.js +5 -0
  242. package/dist/utils/security/rate-limiter.js.map +1 -0
  243. package/dist/utils/telemetry/index.d.ts +2 -0
  244. package/dist/utils/telemetry/index.d.ts.map +1 -0
  245. package/dist/utils/telemetry/index.js +2 -0
  246. package/dist/utils/telemetry/index.js.map +1 -0
  247. package/dist/utils/telemetry/tracing.d.ts +3 -0
  248. package/dist/utils/telemetry/tracing.d.ts.map +1 -0
  249. package/dist/utils/telemetry/tracing.js +4 -0
  250. package/dist/utils/telemetry/tracing.js.map +1 -0
  251. package/package.json +56 -0
@@ -0,0 +1,84 @@
1
+ import { createMcpServer } from "../server.js";
2
+ import { createAuthStrategy, buildServerCardExtras, createTransportEntrypoints, } from "@cesteral/shared";
3
+ import { GAdsHeadersAuthStrategy } from "../../auth/gads-auth-strategy.js";
4
+ import { createSessionServices, sessionServiceStore } from "../../services/session-services.js";
5
+ import { rateLimiter } from "../../utils/platform.js";
6
+ function buildPlatformConfig(config, logger) {
7
+ return {
8
+ authStrategy: config.mcpAuthMode === "gads-headers"
9
+ ? new GAdsHeadersAuthStrategy(logger)
10
+ : createAuthStrategy(config.mcpAuthMode, {
11
+ jwtSecret: config.mcpAuthSecretKey,
12
+ logger,
13
+ }),
14
+ corsAllowHeaders: [
15
+ "Content-Type",
16
+ "Authorization",
17
+ "Mcp-Session-Id",
18
+ "MCP-Protocol-Version",
19
+ "X-GAds-Developer-Token",
20
+ "X-GAds-Client-Id",
21
+ "X-GAds-Client-Secret",
22
+ "X-GAds-Refresh-Token",
23
+ "X-GAds-Login-Customer-Id",
24
+ ],
25
+ authErrorHint: config.mcpAuthMode === "gads-headers"
26
+ ? "Provide Google Ads credentials via X-GAds-Developer-Token, X-GAds-Client-Id, X-GAds-Client-Secret, and X-GAds-Refresh-Token headers."
27
+ : "Provide a valid Bearer token in the Authorization header.",
28
+ sessionServiceStore,
29
+ rateLimiter,
30
+ async createSessionForAuth(authResult, sessionId, appConfig, log) {
31
+ const adapter = authResult.platformAuthAdapter;
32
+ if (adapter) {
33
+ await adapter.validate();
34
+ const services = createSessionServices(adapter, { baseUrl: appConfig.gadsApiBaseUrl }, log, rateLimiter);
35
+ sessionServiceStore.set(sessionId, services, authResult.credentialFingerprint);
36
+ return { services };
37
+ }
38
+ if (appConfig.mcpAuthMode === "none" || appConfig.mcpAuthMode === "jwt") {
39
+ const typedConfig = appConfig;
40
+ if (typedConfig.gadsDeveloperToken &&
41
+ typedConfig.gadsClientId &&
42
+ typedConfig.gadsClientSecret &&
43
+ typedConfig.gadsRefreshToken) {
44
+ const { GAdsRefreshTokenAuthAdapter } = await import("../../auth/gads-auth-adapter.js");
45
+ const envAdapter = new GAdsRefreshTokenAuthAdapter({
46
+ developerToken: typedConfig.gadsDeveloperToken,
47
+ clientId: typedConfig.gadsClientId,
48
+ clientSecret: typedConfig.gadsClientSecret,
49
+ refreshToken: typedConfig.gadsRefreshToken,
50
+ loginCustomerId: typedConfig.gadsLoginCustomerId,
51
+ });
52
+ await envAdapter.validate();
53
+ const services = createSessionServices(envAdapter, { baseUrl: typedConfig.gadsApiBaseUrl }, log, rateLimiter);
54
+ sessionServiceStore.set(sessionId, services, authResult.credentialFingerprint);
55
+ return { services };
56
+ }
57
+ if (appConfig.mcpAuthMode !== "none") {
58
+ return {
59
+ services: null,
60
+ error: {
61
+ message: "Google Ads credentials required. Set GADS_DEVELOPER_TOKEN, GADS_CLIENT_ID, GADS_CLIENT_SECRET, and GADS_REFRESH_TOKEN env vars, or use MCP_AUTH_MODE=gads-headers.",
62
+ status: 400,
63
+ },
64
+ };
65
+ }
66
+ return { services: null };
67
+ }
68
+ return {
69
+ services: null,
70
+ error: {
71
+ message: "Google Ads API credentials required for this server.",
72
+ status: 400,
73
+ },
74
+ };
75
+ },
76
+ async createMcpServer(log, sessionId, gcsBucket) {
77
+ return createMcpServer(log, sessionId, gcsBucket);
78
+ },
79
+ packageJsonPath: new URL("../../../package.json", import.meta.url).pathname,
80
+ serverCard: buildServerCardExtras("gads-mcp"),
81
+ };
82
+ }
83
+ export const { createMcpHttpServer, startHttpServer } = createTransportEntrypoints(buildPlatformConfig);
84
+ //# sourceMappingURL=streamable-http-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streamable-http-transport.js","sourceRoot":"","sources":["../../../src/mcp-server/transports/streamable-http-transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { Logger } from "pino";
2
+ import type { GAdsAuthAdapter } from "../../auth/gads-auth-adapter.js";
3
+ import type { RequestContext } from "@cesteral/shared";
4
+ export declare class GAdsHttpClient {
5
+ private authAdapter;
6
+ private baseUrl;
7
+ private logger;
8
+ constructor(authAdapter: GAdsAuthAdapter, baseUrl: string, logger: Logger);
9
+ get developerToken(): string;
10
+ get loginCustomerId(): string | undefined;
11
+ fetch(path: string, context?: RequestContext, options?: RequestInit): Promise<unknown>;
12
+ }
13
+ //# sourceMappingURL=gads-http-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gads-http-client.d.ts","sourceRoot":"","sources":["../../../src/services/gads/gads-http-client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAEvE,OAAO,KAAK,EAAE,cAAc,EAAe,MAAM,kBAAkB,CAAC;AAyGpE,qBAAa,cAAc;IAEvB,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,MAAM;gBAFN,WAAW,EAAE,eAAe,EAC5B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM;IAGxB,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,IAAI,eAAe,IAAI,MAAM,GAAG,SAAS,CAExC;IAUK,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;CAqC7F"}
@@ -0,0 +1,113 @@
1
+ import { JsonRpcErrorCode, executeWithRetry, fetchWithTimeout } from "@cesteral/shared";
2
+ import { withGAdsApiSpan } from "../../utils/platform.js";
3
+ const GADS_RETRY_CONFIG = {
4
+ maxRetries: 3,
5
+ initialBackoffMs: 1_000,
6
+ maxBackoffMs: 10_000,
7
+ timeoutMs: 30_000,
8
+ platformName: "Google Ads",
9
+ };
10
+ function parseGAdsErrors(body) {
11
+ try {
12
+ const parsed = JSON.parse(body);
13
+ if (parsed.error?.details) {
14
+ const failures = parsed.error.details;
15
+ const messages = [];
16
+ for (const failure of failures) {
17
+ if (failure.errors) {
18
+ for (const err of failure.errors) {
19
+ const code = err.errorCode
20
+ ? Object.entries(err.errorCode)
21
+ .map(([k, v]) => `${k}=${v}`)
22
+ .join(", ")
23
+ : "unknown";
24
+ messages.push(`[${code}] ${err.message || "No message"}`);
25
+ }
26
+ }
27
+ }
28
+ if (messages.length > 0) {
29
+ return messages.join("; ");
30
+ }
31
+ }
32
+ if (parsed.error && typeof parsed.error === "object" && "message" in parsed.error) {
33
+ return parsed.error.message;
34
+ }
35
+ return body.substring(0, 500);
36
+ }
37
+ catch {
38
+ return body.substring(0, 500);
39
+ }
40
+ }
41
+ function mapGAdsStatusCode(status) {
42
+ if (status >= 500)
43
+ return JsonRpcErrorCode.ServiceUnavailable;
44
+ if (status === 429)
45
+ return JsonRpcErrorCode.RateLimited;
46
+ if (status === 403)
47
+ return JsonRpcErrorCode.Forbidden;
48
+ return JsonRpcErrorCode.InvalidRequest;
49
+ }
50
+ function buildGAdsNextAction(status, errorBody, defaultHint) {
51
+ if (status === 401) {
52
+ return "Renew the OAuth access token. Refresh tokens for Google Ads typically expire after extended inactivity — re-run the OAuth consent flow to obtain a new refresh token.";
53
+ }
54
+ if (status === 403) {
55
+ if (/developer.?token/i.test(errorBody)) {
56
+ return "The developer token is missing or not approved for production. Verify the token in the Google Ads UI (Tools & Settings → API Center) and ensure it has standard or basic access for the customer being targeted.";
57
+ }
58
+ return "Verify the authenticated user has permission for this customer ID. Use gads_list_accounts to confirm accessible customer IDs and that the login-customer-id header reflects the manager account hierarchy.";
59
+ }
60
+ if (status === 404) {
61
+ return "Verify the customer/entity ID with gads_list_accounts or gads_list_entities before retrying.";
62
+ }
63
+ return defaultHint;
64
+ }
65
+ export class GAdsHttpClient {
66
+ authAdapter;
67
+ baseUrl;
68
+ logger;
69
+ constructor(authAdapter, baseUrl, logger) {
70
+ this.authAdapter = authAdapter;
71
+ this.baseUrl = baseUrl;
72
+ this.logger = logger;
73
+ }
74
+ get developerToken() {
75
+ return this.authAdapter.developerToken;
76
+ }
77
+ get loginCustomerId() {
78
+ return this.authAdapter.loginCustomerId;
79
+ }
80
+ async fetch(path, context, options) {
81
+ const url = `${this.baseUrl}${path}`;
82
+ const method = options?.method || "GET";
83
+ this.logger.debug({ url, method, requestId: context?.requestId }, "Making Google Ads API request");
84
+ return withGAdsApiSpan(`api.${method}`, path, async (span) => {
85
+ span.setAttribute("http.request.method", method);
86
+ span.setAttribute("http.url", url);
87
+ const result = await executeWithRetry(GADS_RETRY_CONFIG, {
88
+ url,
89
+ fetchOptions: options,
90
+ context,
91
+ logger: this.logger,
92
+ fetchFn: fetchWithTimeout,
93
+ getHeaders: async () => {
94
+ const accessToken = await this.authAdapter.getAccessToken();
95
+ const headers = {
96
+ "Content-Type": "application/json",
97
+ Authorization: `Bearer ${accessToken}`,
98
+ "developer-token": this.authAdapter.developerToken,
99
+ };
100
+ if (this.authAdapter.loginCustomerId) {
101
+ headers["login-customer-id"] = this.authAdapter.loginCustomerId;
102
+ }
103
+ return headers;
104
+ },
105
+ mapStatusCode: (status) => mapGAdsStatusCode(status),
106
+ parseErrorBody: parseGAdsErrors,
107
+ buildNextAction: buildGAdsNextAction,
108
+ });
109
+ return result;
110
+ });
111
+ }
112
+ }
113
+ //# sourceMappingURL=gads-http-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gads-http-client.js","sourceRoot":"","sources":["../../../src/services/gads/gads-http-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import type { Logger } from "pino";
2
+ import type { GAdsHttpClient } from "./gads-http-client.js";
3
+ import type { RateLimiter } from "@cesteral/shared";
4
+ import { type RequestContext } from "@cesteral/shared";
5
+ import { type GAdsEntityType } from "../../mcp-server/tools/utils/entity-mapping.js";
6
+ import type { GoogleAdsQueryRow } from "./types.js";
7
+ export type { GoogleAdsQueryRow };
8
+ export declare class GAdsService {
9
+ private readonly logger;
10
+ private readonly rateLimiter;
11
+ private readonly httpClient;
12
+ constructor(logger: Logger, rateLimiter: RateLimiter, httpClient: GAdsHttpClient);
13
+ gaqlSearch(customerId: string, query: string, pageSize?: number, pageToken?: string, context?: RequestContext): Promise<{
14
+ results: GoogleAdsQueryRow[];
15
+ nextPageToken?: string;
16
+ totalResultsCount?: number;
17
+ }>;
18
+ listAccessibleCustomers(context?: RequestContext): Promise<{
19
+ resourceNames: string[];
20
+ }>;
21
+ getEntity(entityType: GAdsEntityType, customerId: string, entityId: string, context?: RequestContext): Promise<GoogleAdsQueryRow>;
22
+ listEntities(entityType: GAdsEntityType, customerId: string, filters?: Record<string, string>, pageSize?: number, pageToken?: string, orderBy?: string, context?: RequestContext): Promise<{
23
+ entities: GoogleAdsQueryRow[];
24
+ nextPageToken?: string;
25
+ totalResultsCount?: number;
26
+ }>;
27
+ createEntity(entityType: GAdsEntityType, customerId: string, data: Record<string, unknown>, context?: RequestContext): Promise<unknown>;
28
+ updateEntity(entityType: GAdsEntityType, customerId: string, entityId: string, data: Record<string, unknown>, updateMask: string, context?: RequestContext): Promise<unknown>;
29
+ removeEntity(entityType: GAdsEntityType, customerId: string, entityId: string, context?: RequestContext): Promise<unknown>;
30
+ validateEntity(entityType: GAdsEntityType, customerId: string, data: Record<string, unknown>, mode: "create" | "update", entityId?: string, updateMask?: string, context?: RequestContext): Promise<{
31
+ valid: boolean;
32
+ errors?: string[];
33
+ }>;
34
+ bulkMutate(entityType: GAdsEntityType, customerId: string, operations: Array<Record<string, unknown>>, partialFailure?: boolean, context?: RequestContext): Promise<unknown>;
35
+ adjustBids(customerId: string, adjustments: Array<{
36
+ adGroupId: string;
37
+ cpcBidMicros?: string;
38
+ cpmBidMicros?: string;
39
+ }>, context?: RequestContext): Promise<{
40
+ results: Array<{
41
+ adGroupId: string;
42
+ adGroupName?: string;
43
+ success: boolean;
44
+ previousCpcBidMicros?: string;
45
+ previousCpmBidMicros?: string;
46
+ newCpcBidMicros?: string;
47
+ newCpmBidMicros?: string;
48
+ error?: string;
49
+ }>;
50
+ }>;
51
+ private parsePartialFailureErrors;
52
+ bulkUpdateStatus(entityType: GAdsEntityType, customerId: string, entityIds: string[], status: "ENABLED" | "PAUSED" | "REMOVED", context?: RequestContext): Promise<{
53
+ results: Array<{
54
+ entityId: string;
55
+ success: boolean;
56
+ error?: string;
57
+ }>;
58
+ }>;
59
+ }
60
+ //# sourceMappingURL=gads-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gads-service.d.ts","sourceRoot":"","sources":["../../../src/services/gads/gads-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,363 @@
1
+ import { McpError, JsonRpcErrorCode } from "@cesteral/shared";
2
+ import { getEntityConfig, buildMutateUrl, buildResourceName, assertMutateOpSupported, } from "../../mcp-server/tools/utils/entity-mapping.js";
3
+ import { buildListQuery, buildGetByIdQuery } from "../../mcp-server/tools/utils/gaql-helpers.js";
4
+ export class GAdsService {
5
+ logger;
6
+ rateLimiter;
7
+ httpClient;
8
+ constructor(logger, rateLimiter, httpClient) {
9
+ this.logger = logger;
10
+ this.rateLimiter = rateLimiter;
11
+ this.httpClient = httpClient;
12
+ }
13
+ async gaqlSearch(customerId, query, pageSize, pageToken, context) {
14
+ await this.rateLimiter.consume(`gads:${customerId}`);
15
+ this.logger.debug({ customerId, query: query.substring(0, 200) }, "Executing GAQL search");
16
+ const body = {
17
+ query,
18
+ };
19
+ if (pageSize) {
20
+ body.pageSize = pageSize;
21
+ }
22
+ if (pageToken) {
23
+ body.pageToken = pageToken;
24
+ }
25
+ const result = (await this.httpClient.fetch(`/customers/${customerId}/googleAds:search`, context, {
26
+ method: "POST",
27
+ body: JSON.stringify(body),
28
+ }));
29
+ return {
30
+ results: (result.results || []),
31
+ nextPageToken: result.nextPageToken,
32
+ totalResultsCount: result.totalResultsCount,
33
+ };
34
+ }
35
+ async listAccessibleCustomers(context) {
36
+ await this.rateLimiter.consume("gads:global");
37
+ const result = (await this.httpClient.fetch("/customers:listAccessibleCustomers", context, {
38
+ method: "GET",
39
+ }));
40
+ return {
41
+ resourceNames: result.resourceNames || [],
42
+ };
43
+ }
44
+ async getEntity(entityType, customerId, entityId, context) {
45
+ const query = buildGetByIdQuery(entityType, entityId);
46
+ const { results } = await this.gaqlSearch(customerId, query, 1, undefined, context);
47
+ if (results.length === 0) {
48
+ throw new McpError(JsonRpcErrorCode.NotFound, `${entityType} with ID ${entityId} not found in customer ${customerId}`);
49
+ }
50
+ return results[0];
51
+ }
52
+ async listEntities(entityType, customerId, filters, pageSize, pageToken, orderBy, context) {
53
+ const query = buildListQuery(entityType, filters, orderBy);
54
+ const result = await this.gaqlSearch(customerId, query, pageSize, pageToken, context);
55
+ return {
56
+ entities: result.results,
57
+ nextPageToken: result.nextPageToken,
58
+ totalResultsCount: result.totalResultsCount,
59
+ };
60
+ }
61
+ async createEntity(entityType, customerId, data, context) {
62
+ assertMutateOpSupported(entityType, "create");
63
+ await this.rateLimiter.consume(`gads:${customerId}`);
64
+ this.logger.debug({ entityType, customerId }, "Creating Google Ads entity");
65
+ const mutateUrl = buildMutateUrl(entityType, customerId);
66
+ const result = await this.httpClient.fetch(mutateUrl, context, {
67
+ method: "POST",
68
+ body: JSON.stringify({
69
+ operations: [{ create: data }],
70
+ }),
71
+ });
72
+ return result;
73
+ }
74
+ async updateEntity(entityType, customerId, entityId, data, updateMask, context) {
75
+ assertMutateOpSupported(entityType, "update");
76
+ await this.rateLimiter.consume(`gads:${customerId}`);
77
+ this.logger.debug({ entityType, customerId, entityId, updateMask }, "Updating Google Ads entity");
78
+ const resourceName = buildResourceName(entityType, customerId, entityId);
79
+ const mutateUrl = buildMutateUrl(entityType, customerId);
80
+ const result = await this.httpClient.fetch(mutateUrl, context, {
81
+ method: "POST",
82
+ body: JSON.stringify({
83
+ operations: [
84
+ {
85
+ update: { ...data, resourceName },
86
+ updateMask: updateMask,
87
+ },
88
+ ],
89
+ }),
90
+ });
91
+ return result;
92
+ }
93
+ async removeEntity(entityType, customerId, entityId, context) {
94
+ assertMutateOpSupported(entityType, "remove");
95
+ await this.rateLimiter.consume(`gads:${customerId}`);
96
+ this.logger.debug({ entityType, customerId, entityId }, "Removing Google Ads entity");
97
+ const resourceName = buildResourceName(entityType, customerId, entityId);
98
+ const mutateUrl = buildMutateUrl(entityType, customerId);
99
+ const result = await this.httpClient.fetch(mutateUrl, context, {
100
+ method: "POST",
101
+ body: JSON.stringify({
102
+ operations: [{ remove: resourceName }],
103
+ }),
104
+ });
105
+ return result;
106
+ }
107
+ async validateEntity(entityType, customerId, data, mode, entityId, updateMask, context) {
108
+ try {
109
+ assertMutateOpSupported(entityType, mode);
110
+ }
111
+ catch (err) {
112
+ return { valid: false, errors: [err.message] };
113
+ }
114
+ await this.rateLimiter.consume(`gads:${customerId}`);
115
+ this.logger.debug({ entityType, customerId, mode }, "Validating Google Ads entity (dry-run)");
116
+ const mutateUrl = buildMutateUrl(entityType, customerId);
117
+ let operations;
118
+ if (mode === "update") {
119
+ if (!entityId || !updateMask) {
120
+ return {
121
+ valid: false,
122
+ errors: ["entityId and updateMask are required for update mode validation"],
123
+ };
124
+ }
125
+ const resourceName = buildResourceName(entityType, customerId, entityId);
126
+ operations = [
127
+ {
128
+ update: { ...data, resourceName },
129
+ updateMask,
130
+ },
131
+ ];
132
+ }
133
+ else {
134
+ operations = [{ create: data }];
135
+ }
136
+ try {
137
+ await this.httpClient.fetch(mutateUrl, context, {
138
+ method: "POST",
139
+ body: JSON.stringify({
140
+ operations,
141
+ validateOnly: true,
142
+ }),
143
+ });
144
+ return { valid: true };
145
+ }
146
+ catch (error) {
147
+ const err = error;
148
+ const errorMessage = err?.message ?? String(error);
149
+ const errorBody = err?.data?.errorBody ?? errorMessage;
150
+ return { valid: false, errors: [errorBody] };
151
+ }
152
+ }
153
+ async bulkMutate(entityType, customerId, operations, partialFailure, context) {
154
+ for (const op of operations) {
155
+ const kind = "create" in op
156
+ ? "create"
157
+ : "update" in op
158
+ ? "update"
159
+ : "remove" in op
160
+ ? "remove"
161
+ : undefined;
162
+ if (!kind) {
163
+ throw new McpError(JsonRpcErrorCode.InvalidParams, "Each bulk mutate operation must have one of: create, update, remove");
164
+ }
165
+ assertMutateOpSupported(entityType, kind);
166
+ }
167
+ await this.rateLimiter.consume(`gads:${customerId}`);
168
+ this.logger.debug({ entityType, customerId, operationCount: operations.length }, "Executing bulk mutate");
169
+ const mutateUrl = buildMutateUrl(entityType, customerId);
170
+ const body = { operations };
171
+ if (partialFailure) {
172
+ body.partialFailure = true;
173
+ }
174
+ const result = await this.httpClient.fetch(mutateUrl, context, {
175
+ method: "POST",
176
+ body: JSON.stringify(body),
177
+ });
178
+ return result;
179
+ }
180
+ async adjustBids(customerId, adjustments, context) {
181
+ const results = [];
182
+ for (const adjustment of adjustments) {
183
+ try {
184
+ const readQuery = `SELECT ad_group.id, ad_group.name, ad_group.cpc_bid_micros, ad_group.cpm_bid_micros FROM ad_group WHERE ad_group.id = ${adjustment.adGroupId}`;
185
+ const { results: rows } = await this.gaqlSearch(customerId, readQuery, 1, undefined, context);
186
+ if (rows.length === 0) {
187
+ results.push({
188
+ adGroupId: adjustment.adGroupId,
189
+ success: false,
190
+ error: `Ad group ${adjustment.adGroupId} not found in customer ${customerId}`,
191
+ });
192
+ continue;
193
+ }
194
+ const adGroupRow = rows[0];
195
+ const adGroup = adGroupRow.adGroup || {};
196
+ const adGroupName = adGroup.name;
197
+ const previousCpcBidMicros = adGroup.cpcBidMicros?.toString();
198
+ const previousCpmBidMicros = adGroup.cpmBidMicros?.toString();
199
+ const updateData = {};
200
+ const maskFields = [];
201
+ if (adjustment.cpcBidMicros !== undefined) {
202
+ updateData.cpcBidMicros = adjustment.cpcBidMicros;
203
+ maskFields.push("cpcBidMicros");
204
+ }
205
+ if (adjustment.cpmBidMicros !== undefined) {
206
+ updateData.cpmBidMicros = adjustment.cpmBidMicros;
207
+ maskFields.push("cpmBidMicros");
208
+ }
209
+ const resourceName = buildResourceName("adGroup", customerId, adjustment.adGroupId);
210
+ const mutateUrl = buildMutateUrl("adGroup", customerId);
211
+ await this.rateLimiter.consume(`gads:${customerId}`);
212
+ await this.httpClient.fetch(mutateUrl, context, {
213
+ method: "POST",
214
+ body: JSON.stringify({
215
+ operations: [
216
+ {
217
+ update: { ...updateData, resourceName },
218
+ updateMask: maskFields.join(","),
219
+ },
220
+ ],
221
+ }),
222
+ });
223
+ results.push({
224
+ adGroupId: adjustment.adGroupId,
225
+ adGroupName,
226
+ success: true,
227
+ previousCpcBidMicros,
228
+ previousCpmBidMicros,
229
+ newCpcBidMicros: adjustment.cpcBidMicros,
230
+ newCpmBidMicros: adjustment.cpmBidMicros,
231
+ });
232
+ }
233
+ catch (error) {
234
+ const errorMessage = error?.message ?? String(error);
235
+ this.logger.error({ adGroupId: adjustment.adGroupId, error: errorMessage }, "Bid adjustment failed for ad group");
236
+ results.push({
237
+ adGroupId: adjustment.adGroupId,
238
+ success: false,
239
+ error: errorMessage,
240
+ });
241
+ }
242
+ }
243
+ return { results };
244
+ }
245
+ parsePartialFailureErrors(result) {
246
+ const errorsByIndex = new Map();
247
+ const partialErrors = result.partialFailureError ?? null;
248
+ if (!partialErrors)
249
+ return errorsByIndex;
250
+ const errorDetails = (partialErrors.details ?? []);
251
+ for (const detail of errorDetails) {
252
+ const errors = (detail?.errors ?? []);
253
+ for (const err of errors) {
254
+ const location = err?.location;
255
+ const fieldPathElements = (location?.fieldPathElements ?? []);
256
+ const opElement = fieldPathElements.find((el) => el.fieldName === "operations" && el.index != null);
257
+ const opIndex = opElement ? Number(opElement.index) : -1;
258
+ const message = err?.message ??
259
+ (err?.errorCode ? JSON.stringify(err.errorCode) : "Unknown error");
260
+ if (opIndex >= 0) {
261
+ errorsByIndex.set(opIndex, message);
262
+ }
263
+ }
264
+ }
265
+ return errorsByIndex;
266
+ }
267
+ async bulkUpdateStatus(entityType, customerId, entityIds, status, context) {
268
+ await this.rateLimiter.consume(`gads:${customerId}`);
269
+ this.logger.debug({ entityType, customerId, count: entityIds.length, status }, "Executing bulk status update");
270
+ const config = getEntityConfig(entityType);
271
+ const mutateUrl = buildMutateUrl(entityType, customerId);
272
+ if (!config.statusField && status !== "REMOVED") {
273
+ return {
274
+ results: entityIds.map((entityId) => ({
275
+ entityId,
276
+ success: false,
277
+ error: `Entity type "${entityType}" does not have a status field and cannot be set to ${status}`,
278
+ })),
279
+ };
280
+ }
281
+ const statusFieldParts = (config.statusField || "").split(".");
282
+ const statusProperty = statusFieldParts[statusFieldParts.length - 1] || "status";
283
+ if (status === "REMOVED") {
284
+ const operations = entityIds.map((entityId) => ({
285
+ remove: buildResourceName(entityType, customerId, entityId),
286
+ }));
287
+ try {
288
+ const result = (await this.httpClient.fetch(mutateUrl, context, {
289
+ method: "POST",
290
+ body: JSON.stringify({ operations, partialFailure: true }),
291
+ }));
292
+ const errorsByIndex = this.parsePartialFailureErrors(result);
293
+ const mutateResults = result.results || [];
294
+ return {
295
+ results: entityIds.map((entityId, idx) => {
296
+ const error = errorsByIndex.get(idx);
297
+ if (error) {
298
+ return { entityId, success: false, error };
299
+ }
300
+ const resultEntry = mutateResults[idx];
301
+ const hasResult = resultEntry && Object.keys(resultEntry).length > 0;
302
+ return {
303
+ entityId,
304
+ success: hasResult,
305
+ error: !hasResult ? "Operation produced no result" : undefined,
306
+ };
307
+ }),
308
+ };
309
+ }
310
+ catch (error) {
311
+ const errorMessage = error?.message ?? String(error);
312
+ return {
313
+ results: entityIds.map((entityId) => ({
314
+ entityId,
315
+ success: false,
316
+ error: errorMessage,
317
+ })),
318
+ };
319
+ }
320
+ }
321
+ const operations = entityIds.map((entityId) => ({
322
+ update: {
323
+ resourceName: buildResourceName(entityType, customerId, entityId),
324
+ [statusProperty]: status,
325
+ },
326
+ updateMask: statusProperty,
327
+ }));
328
+ try {
329
+ const result = (await this.httpClient.fetch(mutateUrl, context, {
330
+ method: "POST",
331
+ body: JSON.stringify({ operations, partialFailure: true }),
332
+ }));
333
+ const errorsByIndex = this.parsePartialFailureErrors(result);
334
+ const mutateResults = result.results || [];
335
+ return {
336
+ results: entityIds.map((entityId, idx) => {
337
+ const error = errorsByIndex.get(idx);
338
+ if (error) {
339
+ return { entityId, success: false, error };
340
+ }
341
+ const resultEntry = mutateResults[idx];
342
+ const hasResult = resultEntry && Object.keys(resultEntry).length > 0;
343
+ return {
344
+ entityId,
345
+ success: hasResult,
346
+ error: !hasResult ? "Operation produced no result" : undefined,
347
+ };
348
+ }),
349
+ };
350
+ }
351
+ catch (error) {
352
+ const errorMessage = error?.message ?? String(error);
353
+ return {
354
+ results: entityIds.map((entityId) => ({
355
+ entityId,
356
+ success: false,
357
+ error: errorMessage,
358
+ })),
359
+ };
360
+ }
361
+ }
362
+ }
363
+ //# sourceMappingURL=gads-service.js.map