Skip to content

Commit 030f409

Browse files
authored
feat: add Google Ads debug subcommands via MCP (#4)
Add gaql, list-customers, list-assets, upload-asset, pmax-asset-groups, tools-list, and generic call subcommands under `debug google-ads`.
1 parent 8a53b1c commit 030f409

1 file changed

Lines changed: 248 additions & 10 deletions

File tree

‎cmd/debug.go‎

Lines changed: 248 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,63 @@ var (
3737
)
3838

3939
func init() {
40+
// Meta Ads debug
4041
debugMetaAdsCmd.Flags().StringVar(&debugMetaPath, "path", "", "Graph API path, e.g. me/adaccounts or act_123/campaigns (required)")
4142
debugMetaAdsCmd.Flags().StringVar(&debugMetaMethod, "method", "GET", "HTTP method: GET or POST")
4243
debugMetaAdsCmd.Flags().StringVar(&debugMetaParams, "params", "", "JSON object of additional query/body params")
4344
debugMetaAdsCmd.Flags().StringVar(&debugMetaPageID, "page-id", "", "Use this page's access token instead of user token")
4445
debugMetaAdsCmd.MarkFlagRequired("path")
4546
debugCmd.AddCommand(debugMetaAdsCmd)
4647

47-
debugGoogleAdsCmd.Flags().StringVar(&debugGoogleCustomerID, "customer-id", "", "Google Ads customer ID (required)")
48-
debugGoogleAdsCmd.Flags().StringVar(&debugGoogleLoginCustomerID, "login-customer-id", "", "MCC login customer ID (optional)")
49-
debugGoogleAdsCmd.Flags().StringVar(&debugGoogleGAQL, "gaql", "", "GAQL query to execute (required)")
50-
debugGoogleAdsCmd.MarkFlagRequired("customer-id")
51-
debugGoogleAdsCmd.MarkFlagRequired("gaql")
48+
// Google Ads debug subcommands
49+
// gaql
50+
debugGoogleAdsGAQLCmd.Flags().StringVar(&debugGoogleCustomerID, "customer-id", "", "Google Ads customer ID (required)")
51+
debugGoogleAdsGAQLCmd.Flags().StringVar(&debugGoogleLoginCustomerID, "login-customer-id", "", "MCC login customer ID (optional)")
52+
debugGoogleAdsGAQLCmd.Flags().StringVar(&debugGoogleGAQL, "gaql", "", "GAQL query to execute (required)")
53+
debugGoogleAdsGAQLCmd.MarkFlagRequired("customer-id")
54+
debugGoogleAdsGAQLCmd.MarkFlagRequired("gaql")
55+
debugGoogleAdsCmd.AddCommand(debugGoogleAdsGAQLCmd)
56+
57+
// list-assets
58+
debugGoogleAdsListAssetsCmd.Flags().StringVar(&debugGoogleCustomerID, "customer-id", "", "Google Ads customer ID (required)")
59+
debugGoogleAdsListAssetsCmd.Flags().StringVar(&debugGoogleAssetType, "asset-type", "", "Filter by asset type (IMAGE, TEXT, YOUTUBE_VIDEO, etc.)")
60+
debugGoogleAdsListAssetsCmd.Flags().IntVar(&debugGoogleLimit, "limit", 0, "Max assets to return (default: 100)")
61+
debugGoogleAdsListAssetsCmd.MarkFlagRequired("customer-id")
62+
debugGoogleAdsCmd.AddCommand(debugGoogleAdsListAssetsCmd)
63+
64+
// upload-asset
65+
debugGoogleAdsUploadAssetCmd.Flags().StringVar(&debugGoogleCustomerID, "customer-id", "", "Google Ads customer ID (required)")
66+
debugGoogleAdsUploadAssetCmd.Flags().StringVar(&debugGoogleAssetType, "asset-type", "", "Asset type: IMAGE, TEXT, or YOUTUBE_VIDEO (required)")
67+
debugGoogleAdsUploadAssetCmd.Flags().StringVar(&debugGoogleAssetName, "name", "", "Friendly name for the asset")
68+
debugGoogleAdsUploadAssetCmd.Flags().StringVar(&debugGoogleImageURL, "image-url", "", "Public URL of image to upload (for IMAGE type)")
69+
debugGoogleAdsUploadAssetCmd.Flags().StringVar(&debugGoogleText, "text", "", "Text content (for TEXT type)")
70+
debugGoogleAdsUploadAssetCmd.Flags().StringVar(&debugGoogleYouTubeVideoID, "youtube-video-id", "", "YouTube video ID (for YOUTUBE_VIDEO type)")
71+
debugGoogleAdsUploadAssetCmd.MarkFlagRequired("customer-id")
72+
debugGoogleAdsUploadAssetCmd.MarkFlagRequired("asset-type")
73+
debugGoogleAdsCmd.AddCommand(debugGoogleAdsUploadAssetCmd)
74+
75+
// pmax-asset-groups
76+
debugGoogleAdsPmaxAssetGroupsCmd.Flags().StringVar(&debugGoogleCustomerID, "customer-id", "", "Google Ads customer ID (required)")
77+
debugGoogleAdsPmaxAssetGroupsCmd.Flags().StringVar(&debugGoogleCampaignID, "campaign-id", "", "Campaign ID (required)")
78+
debugGoogleAdsPmaxAssetGroupsCmd.MarkFlagRequired("customer-id")
79+
debugGoogleAdsPmaxAssetGroupsCmd.MarkFlagRequired("campaign-id")
80+
debugGoogleAdsCmd.AddCommand(debugGoogleAdsPmaxAssetGroupsCmd)
81+
82+
// list-customers
83+
debugGoogleAdsCmd.AddCommand(debugGoogleAdsListCustomersCmd)
84+
85+
// tools-list
86+
debugGoogleAdsCmd.AddCommand(debugGoogleAdsToolsListCmd)
87+
88+
// call (generic)
89+
debugGoogleAdsCallCmd.Flags().StringVar(&debugGoogleToolName, "tool", "", "Tool name to call (required)")
90+
debugGoogleAdsCallCmd.Flags().StringVar(&debugGoogleToolArgs, "args", "{}", "JSON object of tool arguments")
91+
debugGoogleAdsCallCmd.MarkFlagRequired("tool")
92+
debugGoogleAdsCmd.AddCommand(debugGoogleAdsCallCmd)
93+
5294
debugCmd.AddCommand(debugGoogleAdsCmd)
5395

96+
// TikTok Ads debug
5497
debugTikTokAdsCmd.Flags().StringVar(&debugTikTokAdvertiserID, "advertiser-id", "", "TikTok advertiser ID (required)")
5598
debugTikTokAdsCmd.Flags().StringVar(&debugTikTokEndpoint, "endpoint", "", "TikTok API endpoint path (required)")
5699
debugTikTokAdsCmd.Flags().StringVar(&debugTikTokParams, "params", "{}", "JSON params for the request")
@@ -93,22 +136,217 @@ func runDebugMetaAds(cmd *cobra.Command, args []string) error {
93136
return printJSON(result)
94137
}
95138

96-
// --- Google Ads debug (placeholder - not yet implemented server-side) ---
139+
// --- Google Ads debug via MCP ---
97140

98141
var debugGoogleAdsCmd = &cobra.Command{
99142
Use: "google-ads",
100-
Short: "Send raw GAQL query to Google Ads API",
101-
RunE: runDebugGoogleAds,
143+
Short: "Debug Google Ads via MCP (subcommands: gaql, list-customers, list-assets, upload-asset, pmax-asset-groups, tools-list)",
144+
Long: `Debug Google Ads MCP server tools.
145+
146+
Subcommands:
147+
gaql Execute a raw GAQL query
148+
list-customers List accessible Google Ads customer accounts
149+
list-assets List assets in a Google Ads account
150+
upload-asset Upload a text/image/video asset
151+
pmax-asset-groups List PMax asset groups for a campaign
152+
tools-list List all available tools on the Google Ads MCP server
153+
call Call any tool by name with JSON args`,
154+
}
155+
156+
var debugGoogleAdsGAQLCmd = &cobra.Command{
157+
Use: "gaql",
158+
Short: "Execute a raw GAQL query via MCP",
159+
Long: `Execute a raw GAQL query via the Google Ads MCP server.
160+
161+
Examples:
162+
pipeboard debug google-ads gaql --customer-id 1234567890 --gaql "SELECT campaign.id, campaign.name FROM campaign LIMIT 10"
163+
pipeboard debug google-ads gaql --customer-id 1234567890 --gaql "SELECT asset.id, asset.name, asset.type FROM asset LIMIT 5"`,
164+
RunE: runDebugGoogleAdsGAQL,
165+
}
166+
167+
var debugGoogleAdsListCustomersCmd = &cobra.Command{
168+
Use: "list-customers",
169+
Short: "List accessible Google Ads customer accounts",
170+
RunE: runDebugGoogleAdsListCustomers,
171+
}
172+
173+
var debugGoogleAdsListAssetsCmd = &cobra.Command{
174+
Use: "list-assets",
175+
Short: "List assets in a Google Ads account",
176+
Long: `List creative assets in a Google Ads account.
177+
178+
Examples:
179+
pipeboard debug google-ads list-assets --customer-id 1234567890
180+
pipeboard debug google-ads list-assets --customer-id 1234567890 --asset-type IMAGE
181+
pipeboard debug google-ads list-assets --customer-id 1234567890 --asset-type TEXT --limit 20`,
182+
RunE: runDebugGoogleAdsListAssets,
183+
}
184+
185+
var debugGoogleAdsUploadAssetCmd = &cobra.Command{
186+
Use: "upload-asset",
187+
Short: "Upload an asset (text, image URL, or YouTube video)",
188+
Long: `Upload a creative asset to a Google Ads account.
189+
190+
Examples:
191+
pipeboard debug google-ads upload-asset --customer-id 1234567890 --asset-type TEXT --text "Free shipping today"
192+
pipeboard debug google-ads upload-asset --customer-id 1234567890 --asset-type IMAGE --image-url "https://example.com/banner.jpg" --name "My Banner"
193+
pipeboard debug google-ads upload-asset --customer-id 1234567890 --asset-type YOUTUBE_VIDEO --youtube-video-id "dQw4w9WgXcQ"`,
194+
RunE: runDebugGoogleAdsUploadAsset,
195+
}
196+
197+
var debugGoogleAdsPmaxAssetGroupsCmd = &cobra.Command{
198+
Use: "pmax-asset-groups",
199+
Short: "List PMax asset groups for a campaign",
200+
Long: `List Performance Max asset groups for a campaign.
201+
202+
Examples:
203+
pipeboard debug google-ads pmax-asset-groups --customer-id 1234567890 --campaign-id 9876543210`,
204+
RunE: runDebugGoogleAdsPmaxAssetGroups,
205+
}
206+
207+
var debugGoogleAdsToolsListCmd = &cobra.Command{
208+
Use: "tools-list",
209+
Short: "List all available tools on the Google Ads MCP server",
210+
RunE: runDebugGoogleAdsToolsList,
211+
}
212+
213+
var debugGoogleAdsCallCmd = &cobra.Command{
214+
Use: "call",
215+
Short: "Call any Google Ads MCP tool by name",
216+
Long: `Call any tool on the Google Ads MCP server with JSON arguments.
217+
218+
Examples:
219+
pipeboard debug google-ads call --tool get_google_ads_campaigns --args '{"customer_id":"1234567890"}'
220+
pipeboard debug google-ads call --tool list_google_ads_assets --args '{"customer_id":"1234567890","asset_type":"IMAGE"}'`,
221+
RunE: runDebugGoogleAdsCall,
102222
}
103223

104224
var (
105225
debugGoogleCustomerID string
106226
debugGoogleLoginCustomerID string
107227
debugGoogleGAQL string
228+
debugGoogleAssetType string
229+
debugGoogleLimit int
230+
debugGoogleText string
231+
debugGoogleImageURL string
232+
debugGoogleYouTubeVideoID string
233+
debugGoogleAssetName string
234+
debugGoogleCampaignID string
235+
debugGoogleToolName string
236+
debugGoogleToolArgs string
108237
)
109238

110-
func runDebugGoogleAds(cmd *cobra.Command, args []string) error {
111-
return fmt.Errorf("not yet implemented server-side. Coming soon")
239+
const googleAdsMCPServer = "google-ads-mcp"
240+
241+
func newGoogleAdsMCPClient() (*client.Client, error) {
242+
token, err := getToken()
243+
if err != nil {
244+
return nil, err
245+
}
246+
c := client.New(apiURL, token)
247+
fmt.Fprintf(os.Stderr, "Connecting to %s/%s...\n", apiURL, googleAdsMCPServer)
248+
if err := c.Initialize(googleAdsMCPServer); err != nil {
249+
return nil, fmt.Errorf("MCP handshake failed: %w", err)
250+
}
251+
return c, nil
252+
}
253+
254+
func callGoogleAdsTool(toolName string, args map[string]interface{}) error {
255+
c, err := newGoogleAdsMCPClient()
256+
if err != nil {
257+
return err
258+
}
259+
fmt.Fprintf(os.Stderr, "Calling %s...\n", toolName)
260+
result, err := c.CallTool(googleAdsMCPServer, toolName, args)
261+
if err != nil {
262+
return fmt.Errorf("tool call failed: %w", err)
263+
}
264+
var parsed interface{}
265+
if err := json.Unmarshal(result, &parsed); err != nil {
266+
fmt.Println(string(result))
267+
return nil
268+
}
269+
return printJSON(parsed)
270+
}
271+
272+
func runDebugGoogleAdsGAQL(cmd *cobra.Command, args []string) error {
273+
toolArgs := map[string]interface{}{
274+
"customer_id": debugGoogleCustomerID,
275+
"query": debugGoogleGAQL,
276+
}
277+
if debugGoogleLoginCustomerID != "" {
278+
toolArgs["login_customer_id"] = debugGoogleLoginCustomerID
279+
}
280+
return callGoogleAdsTool("execute_google_ads_gaql_query", toolArgs)
281+
}
282+
283+
func runDebugGoogleAdsListCustomers(cmd *cobra.Command, args []string) error {
284+
return callGoogleAdsTool("list_google_ads_customers", map[string]interface{}{})
285+
}
286+
287+
func runDebugGoogleAdsListAssets(cmd *cobra.Command, args []string) error {
288+
toolArgs := map[string]interface{}{
289+
"customer_id": debugGoogleCustomerID,
290+
}
291+
if debugGoogleAssetType != "" {
292+
toolArgs["asset_type"] = debugGoogleAssetType
293+
}
294+
if debugGoogleLimit > 0 {
295+
toolArgs["limit"] = debugGoogleLimit
296+
}
297+
return callGoogleAdsTool("list_google_ads_assets", toolArgs)
298+
}
299+
300+
func runDebugGoogleAdsUploadAsset(cmd *cobra.Command, args []string) error {
301+
toolArgs := map[string]interface{}{
302+
"customer_id": debugGoogleCustomerID,
303+
"asset_type": debugGoogleAssetType,
304+
}
305+
if debugGoogleAssetName != "" {
306+
toolArgs["name"] = debugGoogleAssetName
307+
}
308+
if debugGoogleImageURL != "" {
309+
toolArgs["image_url"] = debugGoogleImageURL
310+
}
311+
if debugGoogleText != "" {
312+
toolArgs["text"] = debugGoogleText
313+
}
314+
if debugGoogleYouTubeVideoID != "" {
315+
toolArgs["youtube_video_id"] = debugGoogleYouTubeVideoID
316+
}
317+
return callGoogleAdsTool("upload_google_ads_asset", toolArgs)
318+
}
319+
320+
func runDebugGoogleAdsPmaxAssetGroups(cmd *cobra.Command, args []string) error {
321+
toolArgs := map[string]interface{}{
322+
"customer_id": debugGoogleCustomerID,
323+
"campaign_id": debugGoogleCampaignID,
324+
}
325+
return callGoogleAdsTool("get_google_ads_pmax_asset_groups", toolArgs)
326+
}
327+
328+
func runDebugGoogleAdsToolsList(cmd *cobra.Command, args []string) error {
329+
c, err := newGoogleAdsMCPClient()
330+
if err != nil {
331+
return err
332+
}
333+
result, err := c.ListTools(googleAdsMCPServer)
334+
if err != nil {
335+
return fmt.Errorf("listing tools: %w", err)
336+
}
337+
fmt.Fprintf(os.Stderr, "Found %d tools\n\n", len(result.Tools))
338+
for _, tool := range result.Tools {
339+
fmt.Printf("%-45s %s\n", tool.Name, truncate(tool.Description, 80))
340+
}
341+
return nil
342+
}
343+
344+
func runDebugGoogleAdsCall(cmd *cobra.Command, args []string) error {
345+
var toolArgs map[string]interface{}
346+
if err := json.Unmarshal([]byte(debugGoogleToolArgs), &toolArgs); err != nil {
347+
return fmt.Errorf("invalid JSON in --args: %w", err)
348+
}
349+
return callGoogleAdsTool(debugGoogleToolName, toolArgs)
112350
}
113351

114352
// --- TikTok Ads debug (placeholder - not yet implemented server-side) ---

0 commit comments

Comments
 (0)