Skip to content

Commit cffab04

Browse files
authored
Add REST client and rework Meta Ads debug command (#2)
* Add REST client and rework debug commands for Meta Ads - Add internal/client/rest.go with a REST API client for non-MCP endpoints - Rework meta-ads debug to use REST POST to /api/debug/meta-ads with --path, --method, --params, and --page-id flags - Add getWebAPIURL() for the main pipeboard.co domain (vs MCP subdomain) - Stub out Google Ads and TikTok debug commands (not yet server-side) * Add pipeboard binary to .gitignore
1 parent c11bb20 commit cffab04

4 files changed

Lines changed: 131 additions & 73 deletions

File tree

‎.gitignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ go.work.sum
2929

3030
# Build output
3131
bin/
32+
pipeboard
33+
pipeboard.exe
3234

3335
# Conductor workspace
3436
.context/

‎cmd/debug.go‎

Lines changed: 52 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,42 @@ var debugCmd = &cobra.Command{
1515
Hidden: true,
1616
}
1717

18-
// --- Google Ads debug ---
18+
// --- Meta Ads debug ---
1919

20-
var debugGoogleAdsCmd = &cobra.Command{
21-
Use: "google-ads",
22-
Short: "Send raw GAQL query to Google Ads API",
23-
RunE: runDebugGoogleAds,
20+
var debugMetaAdsCmd = &cobra.Command{
21+
Use: "meta-ads",
22+
Short: "Send raw Graph API query to Meta Ads API",
23+
Long: `Send a raw Graph API request through Pipeboard (admin only).
24+
25+
Examples:
26+
pipeboard debug meta-ads --path "me/adaccounts?fields=id,name"
27+
pipeboard debug meta-ads --path "act_123/campaigns" --params '{"fields":"id,name,status"}'
28+
pipeboard debug meta-ads --path "act_123/ads" --method POST --params '{"status":"PAUSED"}'`,
29+
RunE: runDebugMetaAds,
2430
}
2531

2632
var (
27-
debugGoogleCustomerID string
28-
debugGoogleLoginCustomerID string
29-
debugGoogleGAQL string
33+
debugMetaPath string
34+
debugMetaMethod string
35+
debugMetaParams string
36+
debugMetaPageID string
3037
)
3138

3239
func init() {
40+
debugMetaAdsCmd.Flags().StringVar(&debugMetaPath, "path", "", "Graph API path, e.g. me/adaccounts or act_123/campaigns (required)")
41+
debugMetaAdsCmd.Flags().StringVar(&debugMetaMethod, "method", "GET", "HTTP method: GET or POST")
42+
debugMetaAdsCmd.Flags().StringVar(&debugMetaParams, "params", "", "JSON object of additional query/body params")
43+
debugMetaAdsCmd.Flags().StringVar(&debugMetaPageID, "page-id", "", "Use this page's access token instead of user token")
44+
debugMetaAdsCmd.MarkFlagRequired("path")
45+
debugCmd.AddCommand(debugMetaAdsCmd)
46+
3347
debugGoogleAdsCmd.Flags().StringVar(&debugGoogleCustomerID, "customer-id", "", "Google Ads customer ID (required)")
3448
debugGoogleAdsCmd.Flags().StringVar(&debugGoogleLoginCustomerID, "login-customer-id", "", "MCC login customer ID (optional)")
3549
debugGoogleAdsCmd.Flags().StringVar(&debugGoogleGAQL, "gaql", "", "GAQL query to execute (required)")
3650
debugGoogleAdsCmd.MarkFlagRequired("customer-id")
3751
debugGoogleAdsCmd.MarkFlagRequired("gaql")
3852
debugCmd.AddCommand(debugGoogleAdsCmd)
3953

40-
debugMetaAdsCmd.Flags().StringVar(&debugMetaAccountID, "account-id", "", "Meta ad account ID, e.g. act_123 (required)")
41-
debugMetaAdsCmd.Flags().StringVar(&debugMetaPath, "path", "", "Graph API path, e.g. act_123/campaigns?fields=id,name (required)")
42-
debugMetaAdsCmd.MarkFlagRequired("account-id")
43-
debugMetaAdsCmd.MarkFlagRequired("path")
44-
debugCmd.AddCommand(debugMetaAdsCmd)
45-
4654
debugTikTokAdsCmd.Flags().StringVar(&debugTikTokAdvertiserID, "advertiser-id", "", "TikTok advertiser ID (required)")
4755
debugTikTokAdsCmd.Flags().StringVar(&debugTikTokEndpoint, "endpoint", "", "TikTok API endpoint path (required)")
4856
debugTikTokAdsCmd.Flags().StringVar(&debugTikTokParams, "params", "{}", "JSON params for the request")
@@ -51,65 +59,59 @@ func init() {
5159
debugCmd.AddCommand(debugTikTokAdsCmd)
5260
}
5361

54-
func runDebugGoogleAds(cmd *cobra.Command, args []string) error {
62+
func runDebugMetaAds(cmd *cobra.Command, args []string) error {
5563
token, err := getToken()
5664
if err != nil {
5765
return err
5866
}
5967

60-
c := client.New(apiURL, token)
68+
// Parse optional params
69+
var params map[string]string
70+
if debugMetaParams != "" {
71+
if err := json.Unmarshal([]byte(debugMetaParams), &params); err != nil {
72+
return fmt.Errorf("invalid JSON in --params: %w", err)
73+
}
74+
}
6175

62-
params := map[string]interface{}{
63-
"customer_id": debugGoogleCustomerID,
64-
"gaql": debugGoogleGAQL,
76+
body := map[string]interface{}{
77+
"path": debugMetaPath,
78+
"method": debugMetaMethod,
6579
}
66-
if debugGoogleLoginCustomerID != "" {
67-
params["login_customer_id"] = debugGoogleLoginCustomerID
80+
if params != nil {
81+
body["params"] = params
82+
}
83+
if debugMetaPageID != "" {
84+
body["page_id"] = debugMetaPageID
6885
}
6986

70-
result, err := c.CallTool("google-ads-mcp", "debug_query", params)
87+
c := client.NewREST(getWebAPIURL(), token)
88+
result, err := c.Post("/api/debug/meta-ads", body)
7189
if err != nil {
7290
return fmt.Errorf("debug query failed: %w", err)
7391
}
7492

7593
return printJSON(result)
7694
}
7795

78-
// --- Meta Ads debug ---
96+
// --- Google Ads debug (placeholder - not yet implemented server-side) ---
7997

80-
var debugMetaAdsCmd = &cobra.Command{
81-
Use: "meta-ads",
82-
Short: "Send raw Graph API query to Meta Ads API",
83-
RunE: runDebugMetaAds,
98+
var debugGoogleAdsCmd = &cobra.Command{
99+
Use: "google-ads",
100+
Short: "Send raw GAQL query to Google Ads API",
101+
RunE: runDebugGoogleAds,
84102
}
85103

86104
var (
87-
debugMetaAccountID string
88-
debugMetaPath string
105+
debugGoogleCustomerID string
106+
debugGoogleLoginCustomerID string
107+
debugGoogleGAQL string
89108
)
90109

91-
func runDebugMetaAds(cmd *cobra.Command, args []string) error {
92-
token, err := getToken()
93-
if err != nil {
94-
return err
95-
}
96-
97-
c := client.New(apiURL, token)
98-
99-
params := map[string]interface{}{
100-
"account_id": debugMetaAccountID,
101-
"path": debugMetaPath,
102-
}
103-
104-
result, err := c.CallTool("meta-ads-mcp", "debug_query", params)
105-
if err != nil {
106-
return fmt.Errorf("debug query failed: %w", err)
107-
}
108-
109-
return printJSON(result)
110+
func runDebugGoogleAds(cmd *cobra.Command, args []string) error {
111+
return fmt.Errorf("not yet implemented server-side. Coming soon")
110112
}
111113

112-
// --- TikTok Ads debug ---
114+
// --- TikTok Ads debug (placeholder - not yet implemented server-side) ---
113115

114116
var debugTikTokAdsCmd = &cobra.Command{
115117
Use: "tiktok-ads",
@@ -124,30 +126,7 @@ var (
124126
)
125127

126128
func runDebugTikTokAds(cmd *cobra.Command, args []string) error {
127-
token, err := getToken()
128-
if err != nil {
129-
return err
130-
}
131-
132-
c := client.New(apiURL, token)
133-
134-
var parsedParams map[string]interface{}
135-
if err := json.Unmarshal([]byte(debugTikTokParams), &parsedParams); err != nil {
136-
return fmt.Errorf("invalid JSON in --params: %w", err)
137-
}
138-
139-
params := map[string]interface{}{
140-
"advertiser_id": debugTikTokAdvertiserID,
141-
"endpoint": debugTikTokEndpoint,
142-
"params": parsedParams,
143-
}
144-
145-
result, err := c.CallTool("tiktok-ads-mcp", "debug_query", params)
146-
if err != nil {
147-
return fmt.Errorf("debug query failed: %w", err)
148-
}
149-
150-
return printJSON(result)
129+
return fmt.Errorf("not yet implemented server-side. Coming soon")
151130
}
152131

153132
func printJSON(v interface{}) error {

‎cmd/root.go‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ func getDefaultAPIURL() string {
4848
return "https://mcp.pipeboard.co"
4949
}
5050

51+
// getWebAPIURL returns the base URL for Pipeboard web API endpoints (not MCP).
52+
// Debug and other REST endpoints live on the main domain, not on the MCP subdomain.
53+
func getWebAPIURL() string {
54+
if url := os.Getenv("PIPEBOARD_WEB_URL"); url != "" {
55+
return url
56+
}
57+
return "https://pipeboard.co"
58+
}
59+
5160
func getToken() (string, error) {
5261
// 1. Check environment variable
5362
if token := os.Getenv("PIPEBOARD_API_TOKEN"); token != "" {

‎internal/client/rest.go‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package client
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
)
10+
11+
// RESTClient calls Pipeboard REST API endpoints.
12+
type RESTClient struct {
13+
baseURL string
14+
token string
15+
httpClient *http.Client
16+
}
17+
18+
// NewREST creates a REST API client.
19+
func NewREST(baseURL, token string) *RESTClient {
20+
return &RESTClient{
21+
baseURL: baseURL,
22+
token: token,
23+
httpClient: &http.Client{},
24+
}
25+
}
26+
27+
// Post sends a JSON POST request and returns the parsed response.
28+
func (c *RESTClient) Post(path string, body interface{}) (map[string]interface{}, error) {
29+
data, err := json.Marshal(body)
30+
if err != nil {
31+
return nil, fmt.Errorf("marshaling request: %w", err)
32+
}
33+
34+
url := fmt.Sprintf("%s%s", c.baseURL, path)
35+
req, err := http.NewRequest("POST", url, bytes.NewReader(data))
36+
if err != nil {
37+
return nil, fmt.Errorf("creating request: %w", err)
38+
}
39+
40+
req.Header.Set("Content-Type", "application/json")
41+
req.Header.Set("Authorization", "Bearer "+c.token)
42+
43+
resp, err := c.httpClient.Do(req)
44+
if err != nil {
45+
return nil, fmt.Errorf("sending request: %w", err)
46+
}
47+
defer resp.Body.Close()
48+
49+
respBody, err := io.ReadAll(resp.Body)
50+
if err != nil {
51+
return nil, fmt.Errorf("reading response: %w", err)
52+
}
53+
54+
var result map[string]interface{}
55+
if err := json.Unmarshal(respBody, &result); err != nil {
56+
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(respBody))
57+
}
58+
59+
if resp.StatusCode == 401 || resp.StatusCode == 403 {
60+
desc, _ := result["error_description"].(string)
61+
if desc == "" {
62+
desc, _ = result["error"].(string)
63+
}
64+
return nil, fmt.Errorf("%s (HTTP %d)", desc, resp.StatusCode)
65+
}
66+
67+
return result, nil
68+
}

0 commit comments

Comments
 (0)