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 +19 -16
- package/dist/build-info.json +1 -1
- package/dist/index.js +16 -5
- package/dist/resilience.js +42 -18
- package/package.json +1 -1
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
|
-
-
|
|
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()`
|
|
121
|
-
- `bing_ads_get_campaign_performance(start_date, end_date)`
|
|
122
|
-
- `bing_ads_update_campaign_budget(campaign_id, daily_budget)`
|
|
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)`
|
|
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])`
|
|
129
|
-
- `bing_ads_search_term_report(start_date, end_date)`
|
|
130
|
-
- `bing_ads_pause_keywords(ad_group_id, keyword_ids)`
|
|
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])`
|
|
134
|
-
- `bing_ads_add_shared_negatives(list_id, keywords)`
|
|
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
|
|
200
|
-
- `
|
|
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)
|
|
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
|
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"sha":"
|
|
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 {
|
|
113
|
-
|
|
114
|
-
|
|
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:
|
|
558
|
-
version:
|
|
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);
|
package/dist/resilience.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { retry, circuitBreaker, wrap,
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
78
|
+
const retryPolicy = retry(isTransient, {
|
|
55
79
|
maxAttempts: 3,
|
|
56
80
|
backoff,
|
|
57
81
|
});
|
|
58
|
-
const circuitBreakerPolicy = circuitBreaker(
|
|
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
|
+
"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": {
|