mcp-bing-ads 1.0.4 → 1.0.6

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/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  Production-grade MCP server for Microsoft Advertising (Bing Ads) API. Enables Claude to manage Bing/Microsoft Ads accounts with full campaign, ad group, keyword, and performance analysis capabilities.
9
9
 
10
10
  **Features:**
11
- - 30+ production-tested tools
11
+ - 10 production-tested tools
12
12
  - Campaign, ad group, and keyword management
13
13
  - Keyword performance analysis with quality scores
14
14
  - Search term reporting & bid automation
@@ -58,10 +58,11 @@ npm install mcp-bing-ads
58
58
 
59
59
  4. **Set environment variables (recommended):**
60
60
  ```bash
61
+ export BING_ADS_DEVELOPER_TOKEN="your_developer_token"
61
62
  export BING_ADS_CLIENT_ID="your_client_id"
63
+ export BING_ADS_REFRESH_TOKEN="your_refresh_token"
64
+ # Optional:
62
65
  export BING_ADS_CLIENT_SECRET="your_client_secret"
63
- export BING_CUSTOMER_ID="your_customer_id"
64
- export BING_ACCOUNT_ID="your_account_id"
65
66
  ```
66
67
 
67
68
  ## Usage
@@ -116,23 +117,25 @@ bing_ads_add_shared_negatives({
116
117
 
117
118
  ## API Reference
118
119
 
120
+ ### Context
121
+ - `bing_ads_get_client_context(working_directory)` -- Detect account from working directory
122
+
119
123
  ### Campaigns
120
- - `bing_ads_list_campaigns()` List all campaigns
121
- - `bing_ads_get_campaign_performance(start_date, end_date)` Campaign metrics
122
- - `bing_ads_update_campaign_budget(campaign_id, daily_budget)` Update daily spend
124
+ - `bing_ads_list_campaigns()` -- List all campaigns
125
+ - `bing_ads_get_campaign_performance(start_date, end_date)` -- Campaign metrics
126
+ - `bing_ads_update_campaign_budget(campaign_id, daily_budget)` -- Update daily spend
123
127
 
124
128
  ### Ad Groups
125
- - `bing_ads_list_ad_groups(campaign_id)` List ad groups in campaign
129
+ - `bing_ads_list_ad_groups(campaign_id)` -- List ad groups in campaign
126
130
 
127
131
  ### Keywords
128
- - `bing_ads_keyword_performance(start_date, end_date, [campaign_ids])` Keyword metrics & QS
129
- - `bing_ads_search_term_report(start_date, end_date)` Search terms that triggered ads
130
- - `bing_ads_pause_keywords(ad_group_id, keyword_ids)` Pause keywords
132
+ - `bing_ads_keyword_performance(start_date, end_date, [campaign_ids])` -- Keyword metrics & QS
133
+ - `bing_ads_search_term_report(start_date, end_date)` -- Search terms that triggered ads
134
+ - `bing_ads_pause_keywords(ad_group_id, keyword_ids)` -- Pause keywords
131
135
 
132
136
  ### Negative Keywords
133
- - `bing_ads_list_shared_entities([type])` List shared negative lists
134
- - `bing_ads_add_shared_negatives(list_id, keywords)` Add to shared list
135
- - `bing_ads_add_campaign_negatives(campaign_id, keywords)` — Campaign-level negatives
137
+ - `bing_ads_list_shared_entities([type])` -- List shared negative lists
138
+ - `bing_ads_add_shared_negatives(list_id, keywords)` -- Add to shared list
136
139
 
137
140
  ### Performance Reports
138
141
  - Campaign performance (ROI, conversions, CTR, CPC)
@@ -196,8 +199,8 @@ cp config.example.json config.json
196
199
 
197
200
  ### `Missing required credentials`
198
201
  Check that:
199
- - `BING_ADS_CLIENT_ID` and `BING_ADS_CLIENT_SECRET` are set (or in config.json)
200
- - `BING_CUSTOMER_ID` and `BING_ACCOUNT_ID` are provided
202
+ - `BING_ADS_DEVELOPER_TOKEN`, `BING_ADS_CLIENT_ID`, and `BING_ADS_REFRESH_TOKEN` are set
203
+ - `BING_ADS_CLIENT_SECRET` is set (if using a confidential app)
201
204
  - OAuth token is valid (expires, may need refresh)
202
205
 
203
206
  ### `Rate limit exceeded`
@@ -235,6 +238,6 @@ Contributions welcome! Please:
235
238
 
236
239
  Built with production workloads in mind: resilient API calls (circuit breakers, retry with backoff, response truncation), full Quality Score diagnostics, and negative keyword management at scale.
237
240
 
238
- **Also by Mark:** [mcp-linkedin-ads](https://github.com/mharnett/mcp-linkedin-ads) LinkedIn Ads MCP server with 65+ tools.
241
+ **Also by Mark:** [mcp-linkedin-ads](https://github.com/mharnett/mcp-linkedin-ads) -- LinkedIn Ads MCP server with 7 tools.
239
242
 
240
243
  **Last Updated:** 2026-03-13
@@ -1 +1 @@
1
- {"sha":"ded62e5","builtAt":"2026-04-09T19:36:59.250Z"}
1
+ {"sha":"60a061a","builtAt":"2026-04-09T21:18:35.074Z"}
package/dist/index.js CHANGED
@@ -109,9 +109,12 @@ class BingAdsManager {
109
109
  if (data.refresh_token && data.refresh_token !== this.refreshToken) {
110
110
  this.refreshToken = data.refresh_token;
111
111
  try {
112
- const { execSync } = await import("child_process");
113
- execSync(`security delete-generic-password -a bing-ads-mcp -s BING_ADS_REFRESH_TOKEN 2>/dev/null; ` +
114
- `security add-generic-password -a bing-ads-mcp -s BING_ADS_REFRESH_TOKEN -w "${data.refresh_token}"`);
112
+ const { execFileSync } = await import("child_process");
113
+ try {
114
+ execFileSync("security", ["delete-generic-password", "-a", "bing-ads-mcp", "-s", "BING_ADS_REFRESH_TOKEN"], { stdio: "ignore" });
115
+ }
116
+ catch { /* may not exist yet */ }
117
+ execFileSync("security", ["add-generic-password", "-a", "bing-ads-mcp", "-s", "BING_ADS_REFRESH_TOKEN", "-w", data.refresh_token]);
115
118
  console.error("[token] Rotated refresh token persisted to Keychain");
116
119
  }
117
120
  catch (err) {
@@ -554,8 +557,8 @@ class BingAdsManager {
554
557
  const config = loadConfig();
555
558
  const adsManager = new BingAdsManager(config);
556
559
  const server = new Server({
557
- name: "mcp-bing-ads",
558
- version: "1.0.0",
560
+ name: __cliPkg.name,
561
+ version: __cliPkg.version,
559
562
  }, {
560
563
  capabilities: {
561
564
  tools: {},
@@ -768,4 +771,12 @@ async function main() {
768
771
  await server.connect(transport);
769
772
  console.error("[startup] MCP Bing Ads server running");
770
773
  }
774
+ process.on("SIGTERM", () => {
775
+ console.error("[shutdown] SIGTERM received, exiting");
776
+ process.exit(0);
777
+ });
778
+ process.on("SIGINT", () => {
779
+ console.error("[shutdown] SIGINT received, exiting");
780
+ process.exit(0);
781
+ });
771
782
  main().catch(console.error);
@@ -1,4 +1,4 @@
1
- import { retry, circuitBreaker, wrap, handleAll, timeout, TimeoutStrategy, ExponentialBackoff, ConsecutiveBreaker, } from "cockatiel";
1
+ import { retry, circuitBreaker, wrap, handleWhen, timeout, TimeoutStrategy, ExponentialBackoff, ConsecutiveBreaker, } from "cockatiel";
2
2
  import pino from "pino";
3
3
  // ============================================
4
4
  // LOGGER
@@ -21,27 +21,38 @@ export const logger = pino({
21
21
  // ============================================
22
22
  const MAX_RESPONSE_SIZE = 200_000; // 200KB
23
23
  export function safeResponse(data, context) {
24
- const jsonStr = JSON.stringify(data);
25
- const sizeBytes = Buffer.byteLength(jsonStr, "utf-8");
26
- if (sizeBytes > MAX_RESPONSE_SIZE) {
27
- logger.warn({ sizeBytes, maxSize: MAX_RESPONSE_SIZE, context }, `Response exceeds size limit, truncating`);
28
- // If it's an array, truncate
29
- if (Array.isArray(data)) {
30
- const truncated = data.slice(0, Math.max(1, Math.floor(data.length * 0.5)));
31
- return truncated;
24
+ let current = data;
25
+ for (let pass = 0; pass < 10; pass++) {
26
+ const jsonStr = JSON.stringify(current);
27
+ const sizeBytes = Buffer.byteLength(jsonStr, "utf-8");
28
+ if (sizeBytes <= MAX_RESPONSE_SIZE)
29
+ return current;
30
+ logger.warn({ sizeBytes, maxSize: MAX_RESPONSE_SIZE, context, pass }, "Response exceeds size limit, truncating");
31
+ if (Array.isArray(current)) {
32
+ current = current.slice(0, Math.max(1, Math.floor(current.length * 0.5)));
33
+ continue;
32
34
  }
33
- // If it's an object with items/results, truncate those
34
- if (typeof data === "object" && data !== null) {
35
- const obj = data;
36
- for (const key of ["items", "results", "data", "rows"]) {
37
- if (Array.isArray(obj[key])) {
35
+ if (typeof current === "object" && current !== null) {
36
+ const obj = current;
37
+ let truncated = false;
38
+ for (const key of ["items", "results", "data", "rows", "tags", "triggers", "variables"]) {
39
+ if (Array.isArray(obj[key]) && obj[key].length > 1) {
38
40
  obj[key] = obj[key].slice(0, Math.max(1, Math.floor(obj[key].length * 0.5)));
39
- return obj;
41
+ if ("count" in obj)
42
+ obj.count = obj[key].length;
43
+ if ("row_count" in obj)
44
+ obj.row_count = obj[key].length;
45
+ obj.truncated = true;
46
+ truncated = true;
47
+ break;
40
48
  }
41
49
  }
50
+ if (truncated)
51
+ continue;
42
52
  }
53
+ break;
43
54
  }
44
- return data;
55
+ return current;
45
56
  }
46
57
  // ============================================
47
58
  // RETRY + CIRCUIT BREAKER + TIMEOUT
@@ -50,12 +61,25 @@ const backoff = new ExponentialBackoff({
50
61
  initialDelay: 100,
51
62
  maxDelay: 5_000,
52
63
  });
64
+ const isTransient = handleWhen((err) => {
65
+ const msg = (err?.message || "").toLowerCase();
66
+ const code = err?.code || err?.status;
67
+ if (code === 401 || code === 403 || code === 7 || code === 16)
68
+ return false;
69
+ if (msg.includes("unauthenticated") || msg.includes("permission_denied") || msg.includes("invalid_grant"))
70
+ return false;
71
+ if (code === 429 || msg.includes("rate"))
72
+ return true;
73
+ if (code >= 400 && code < 500)
74
+ return false;
75
+ return true;
76
+ });
53
77
  // Individual policies
54
- const retryPolicy = retry(handleAll, {
78
+ const retryPolicy = retry(isTransient, {
55
79
  maxAttempts: 3,
56
80
  backoff,
57
81
  });
58
- const circuitBreakerPolicy = circuitBreaker(handleAll, {
82
+ const circuitBreakerPolicy = circuitBreaker(isTransient, {
59
83
  halfOpenAfter: 60_000, // 60s to attempt recovery
60
84
  breaker: new ConsecutiveBreaker(5), // Open after 5 consecutive failures
61
85
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-bing-ads",
3
3
  "mcpName": "io.github.mharnett/bing-ads",
4
- "version": "1.0.4",
4
+ "version": "1.0.6",
5
5
  "description": "MCP server for Microsoft Advertising (Bing Ads) API with campaign, ad group, keyword, and performance reporting. First comprehensive open-source Bing Ads MCP.",
6
6
  "main": "dist/index.js",
7
7
  "bin": {