@appsyogi/adsense-mcp-server 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,1645 @@
1
+ // src/server/index.ts
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import {
5
+ CallToolRequestSchema,
6
+ ListToolsRequestSchema,
7
+ ListResourcesRequestSchema,
8
+ ReadResourceRequestSchema
9
+ } from "@modelcontextprotocol/sdk/types.js";
10
+
11
+ // src/adsense/client.ts
12
+ import { google as google2 } from "googleapis";
13
+
14
+ // src/auth/tokenStore.ts
15
+ import * as fs from "fs/promises";
16
+ import * as path from "path";
17
+ import envPaths from "env-paths";
18
+ var paths = envPaths("adsense-mcp");
19
+ var KEYTAR_SERVICE = "adsense-mcp";
20
+ var KEYTAR_ACCOUNT = "tokens";
21
+ async function ensureConfigDir() {
22
+ await fs.mkdir(paths.config, { recursive: true });
23
+ }
24
+ async function loadConfig() {
25
+ await ensureConfigDir();
26
+ const configPath = path.join(paths.config, "config.json");
27
+ try {
28
+ const content = await fs.readFile(configPath, "utf-8");
29
+ return JSON.parse(content);
30
+ } catch {
31
+ return {
32
+ authType: "oauth",
33
+ scope: "readonly"
34
+ };
35
+ }
36
+ }
37
+ async function loadTokens() {
38
+ try {
39
+ const keytar = await import("keytar");
40
+ const stored = await keytar.getPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT);
41
+ if (stored) {
42
+ return JSON.parse(stored);
43
+ }
44
+ } catch {
45
+ }
46
+ await ensureConfigDir();
47
+ const tokensPath = path.join(paths.config, "tokens.json");
48
+ try {
49
+ const content = await fs.readFile(tokensPath, "utf-8");
50
+ return JSON.parse(content);
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+ async function saveTokens(tokens) {
56
+ const tokenString = JSON.stringify(tokens);
57
+ try {
58
+ const keytar = await import("keytar");
59
+ await keytar.setPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT, tokenString);
60
+ return;
61
+ } catch {
62
+ }
63
+ await ensureConfigDir();
64
+ const tokensPath = path.join(paths.config, "tokens.json");
65
+ await fs.writeFile(tokensPath, tokenString, { mode: 384 });
66
+ }
67
+ function getCachePath() {
68
+ return path.join(paths.config, "cache.sqlite");
69
+ }
70
+
71
+ // src/auth/oauth.ts
72
+ import { OAuth2Client } from "google-auth-library";
73
+
74
+ // src/types.ts
75
+ var ADSENSE_SCOPES = {
76
+ readonly: ["https://www.googleapis.com/auth/adsense.readonly"]
77
+ // Full scope available but NOT recommended for MCP use:
78
+ // full: ['https://www.googleapis.com/auth/adsense'],
79
+ };
80
+
81
+ // src/auth/oauth.ts
82
+ async function createOAuthClient() {
83
+ const config = await loadConfig();
84
+ if (config.authType !== "oauth") {
85
+ throw new Error("OAuth not configured. Run `adsense-mcp init` to set up OAuth.");
86
+ }
87
+ const oauth2Client = new OAuth2Client({
88
+ clientId: config.clientId,
89
+ clientSecret: config.clientSecret
90
+ });
91
+ const tokens = await loadTokens();
92
+ if (!tokens) {
93
+ throw new Error("No tokens found. Run `adsense-mcp init` to authenticate.");
94
+ }
95
+ oauth2Client.setCredentials({
96
+ access_token: tokens.accessToken,
97
+ refresh_token: tokens.refreshToken,
98
+ expiry_date: tokens.expiryDate
99
+ });
100
+ oauth2Client.on("tokens", async (newTokens) => {
101
+ await saveTokens({
102
+ accessToken: newTokens.access_token || tokens.accessToken,
103
+ refreshToken: newTokens.refresh_token || tokens.refreshToken,
104
+ expiryDate: newTokens.expiry_date || Date.now() + 36e5
105
+ });
106
+ });
107
+ return oauth2Client;
108
+ }
109
+
110
+ // src/auth/serviceAccount.ts
111
+ import { google } from "googleapis";
112
+ import * as fs2 from "fs/promises";
113
+ async function createServiceAccountClient() {
114
+ const config = await loadConfig();
115
+ if (config.authType !== "service-account") {
116
+ throw new Error("Service account not configured. Run `adsense-mcp init --service-account <path>` to set up.");
117
+ }
118
+ if (!config.serviceAccountPath) {
119
+ throw new Error("Service account path not configured.");
120
+ }
121
+ const keyFileContent = await fs2.readFile(config.serviceAccountPath, "utf-8");
122
+ const keyFile = JSON.parse(keyFileContent);
123
+ const auth = new google.auth.JWT({
124
+ email: keyFile.client_email,
125
+ key: keyFile.private_key,
126
+ scopes: ADSENSE_SCOPES[config.scope]
127
+ });
128
+ return auth;
129
+ }
130
+
131
+ // src/adsense/rateLimiter.ts
132
+ var MAX_REQUESTS_PER_MINUTE = 100;
133
+ var MAX_RETRIES = 5;
134
+ var BASE_DELAY_MS = 1e3;
135
+ var MAX_DELAY_MS = 32e3;
136
+ var RateLimiter = class {
137
+ requestTimestamps = [];
138
+ /**
139
+ * Wait if necessary to stay under rate limit
140
+ */
141
+ async throttle() {
142
+ const now = Date.now();
143
+ const oneMinuteAgo = now - 6e4;
144
+ this.requestTimestamps = this.requestTimestamps.filter(
145
+ (ts) => ts > oneMinuteAgo
146
+ );
147
+ if (this.requestTimestamps.length >= MAX_REQUESTS_PER_MINUTE) {
148
+ const oldestInWindow = this.requestTimestamps[0];
149
+ const waitTime = oldestInWindow + 6e4 - now + 100;
150
+ if (waitTime > 0) {
151
+ await sleep(waitTime);
152
+ }
153
+ }
154
+ this.requestTimestamps.push(Date.now());
155
+ }
156
+ /**
157
+ * Get current request count in the last minute
158
+ */
159
+ getRequestCount() {
160
+ const oneMinuteAgo = Date.now() - 6e4;
161
+ return this.requestTimestamps.filter((ts) => ts > oneMinuteAgo).length;
162
+ }
163
+ /**
164
+ * Check if we're close to the rate limit
165
+ */
166
+ isNearLimit() {
167
+ return this.getRequestCount() >= MAX_REQUESTS_PER_MINUTE * 0.8;
168
+ }
169
+ };
170
+ async function withRetry(operation, retries = MAX_RETRIES) {
171
+ let lastError;
172
+ for (let attempt = 0; attempt < retries; attempt++) {
173
+ try {
174
+ return await operation();
175
+ } catch (error) {
176
+ lastError = error;
177
+ const isRateLimited = error.code === 429 || error.status === 429 || error.message?.includes("quota") || error.message?.includes("rate limit") || error.message?.includes("RATE_LIMIT_EXCEEDED");
178
+ const isRetryable = isRateLimited || error.code === 503 || error.code === 500 || error.status === 503 || error.status === 500 || error.code === "ECONNRESET" || error.code === "ETIMEDOUT";
179
+ if (!isRetryable || attempt === retries - 1) {
180
+ throw error;
181
+ }
182
+ const delay = Math.min(
183
+ BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 1e3,
184
+ MAX_DELAY_MS
185
+ );
186
+ console.error(
187
+ `Request failed (attempt ${attempt + 1}/${retries}), retrying in ${Math.round(delay)}ms...`
188
+ );
189
+ await sleep(delay);
190
+ }
191
+ }
192
+ throw lastError || new Error("Max retries exceeded");
193
+ }
194
+ function sleep(ms) {
195
+ return new Promise((resolve) => setTimeout(resolve, ms));
196
+ }
197
+ var rateLimiter = new RateLimiter();
198
+
199
+ // src/adsense/cache.ts
200
+ import Database from "better-sqlite3";
201
+ import * as crypto from "crypto";
202
+ var CacheTTL = {
203
+ TODAY_EARNINGS: 5 * 60 * 1e3,
204
+ // 5 minutes
205
+ YESTERDAY_EARNINGS: 60 * 60 * 1e3,
206
+ // 1 hour
207
+ HISTORICAL_REPORT: 24 * 60 * 60 * 1e3,
208
+ // 24 hours
209
+ ACCOUNTS: 24 * 60 * 60 * 1e3,
210
+ // 24 hours
211
+ SITES: 60 * 60 * 1e3,
212
+ // 1 hour
213
+ ALERTS: 15 * 60 * 1e3,
214
+ // 15 minutes
215
+ POLICY_ISSUES: 30 * 60 * 1e3,
216
+ // 30 minutes
217
+ PAYMENTS: 6 * 60 * 60 * 1e3,
218
+ // 6 hours
219
+ AD_UNITS: 60 * 60 * 1e3
220
+ // 1 hour
221
+ };
222
+ var CacheManager = class {
223
+ db;
224
+ constructor(dbPath) {
225
+ this.db = new Database(dbPath || getCachePath());
226
+ this.initSchema();
227
+ }
228
+ /**
229
+ * Initialize database schema
230
+ */
231
+ initSchema() {
232
+ this.db.exec(`
233
+ -- Report cache with TTL
234
+ CREATE TABLE IF NOT EXISTS report_cache (
235
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
236
+ cache_key TEXT UNIQUE NOT NULL,
237
+ account_id TEXT NOT NULL,
238
+ query_hash TEXT NOT NULL,
239
+ response_data TEXT NOT NULL,
240
+ created_at INTEGER NOT NULL,
241
+ expires_at INTEGER NOT NULL
242
+ );
243
+
244
+ CREATE INDEX IF NOT EXISTS idx_report_cache_key ON report_cache(cache_key);
245
+ CREATE INDEX IF NOT EXISTS idx_report_cache_expires ON report_cache(expires_at);
246
+
247
+ -- Account info cache
248
+ CREATE TABLE IF NOT EXISTS accounts_cache (
249
+ account_id TEXT PRIMARY KEY,
250
+ account_data TEXT NOT NULL,
251
+ updated_at INTEGER NOT NULL
252
+ );
253
+
254
+ -- Query history for analytics
255
+ CREATE TABLE IF NOT EXISTS query_history (
256
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
257
+ account_id TEXT NOT NULL,
258
+ tool_name TEXT NOT NULL,
259
+ query_params TEXT,
260
+ executed_at INTEGER NOT NULL,
261
+ response_time_ms INTEGER
262
+ );
263
+ `);
264
+ }
265
+ /**
266
+ * Generate a cache key from query parameters
267
+ */
268
+ generateCacheKey(prefix, params) {
269
+ const hash = crypto.createHash("md5").update(JSON.stringify(params)).digest("hex");
270
+ return `${prefix}:${hash}`;
271
+ }
272
+ /**
273
+ * Get cached data if not expired
274
+ */
275
+ get(prefix, params) {
276
+ const cacheKey = this.generateCacheKey(prefix, params);
277
+ const now = Date.now();
278
+ const row = this.db.prepare("SELECT response_data, expires_at FROM report_cache WHERE cache_key = ?").get(cacheKey);
279
+ if (row && row.expires_at > now) {
280
+ return JSON.parse(row.response_data);
281
+ }
282
+ return null;
283
+ }
284
+ /**
285
+ * Store data in cache
286
+ */
287
+ set(prefix, params, data, ttl, accountId) {
288
+ const cacheKey = this.generateCacheKey(prefix, params);
289
+ const queryHash = crypto.createHash("md5").update(JSON.stringify(params)).digest("hex");
290
+ const now = Date.now();
291
+ this.db.prepare(`
292
+ INSERT OR REPLACE INTO report_cache
293
+ (cache_key, account_id, query_hash, response_data, created_at, expires_at)
294
+ VALUES (?, ?, ?, ?, ?, ?)
295
+ `).run(cacheKey, accountId, queryHash, JSON.stringify(data), now, now + ttl);
296
+ }
297
+ /**
298
+ * Clear expired cache entries
299
+ */
300
+ clearExpired() {
301
+ const result = this.db.prepare("DELETE FROM report_cache WHERE expires_at < ?").run(Date.now());
302
+ return result.changes;
303
+ }
304
+ /**
305
+ * Clear all cache for an account
306
+ */
307
+ clearAccount(accountId) {
308
+ const result = this.db.prepare("DELETE FROM report_cache WHERE account_id = ?").run(accountId);
309
+ return result.changes;
310
+ }
311
+ /**
312
+ * Clear all cache
313
+ */
314
+ clearAll() {
315
+ this.db.exec("DELETE FROM report_cache");
316
+ this.db.exec("DELETE FROM accounts_cache");
317
+ }
318
+ /**
319
+ * Record a query for analytics
320
+ */
321
+ recordQuery(accountId, toolName, params, responseTimeMs) {
322
+ this.db.prepare(`
323
+ INSERT INTO query_history (account_id, tool_name, query_params, executed_at, response_time_ms)
324
+ VALUES (?, ?, ?, ?, ?)
325
+ `).run(accountId, toolName, JSON.stringify(params), Date.now(), responseTimeMs);
326
+ }
327
+ /**
328
+ * Get cache statistics
329
+ */
330
+ getStats() {
331
+ const now = Date.now();
332
+ const totalEntries = this.db.prepare("SELECT COUNT(*) as count FROM report_cache").get().count;
333
+ const expiredCount = this.db.prepare("SELECT COUNT(*) as count FROM report_cache WHERE expires_at < ?").get(now).count;
334
+ const sizeResult = this.db.prepare("SELECT SUM(LENGTH(response_data)) as size FROM report_cache").get();
335
+ return {
336
+ totalEntries,
337
+ totalSize: sizeResult.size || 0,
338
+ expiredCount
339
+ };
340
+ }
341
+ /**
342
+ * Close database connection
343
+ */
344
+ close() {
345
+ this.db.close();
346
+ }
347
+ };
348
+ var cacheInstance = null;
349
+ function getCache() {
350
+ if (!cacheInstance) {
351
+ cacheInstance = new CacheManager();
352
+ }
353
+ return cacheInstance;
354
+ }
355
+
356
+ // src/adsense/client.ts
357
+ var AdSenseClient = class _AdSenseClient {
358
+ adsense;
359
+ defaultAccountId;
360
+ constructor(adsense, defaultAccountId) {
361
+ this.adsense = adsense;
362
+ this.defaultAccountId = defaultAccountId;
363
+ }
364
+ /**
365
+ * Create an authenticated AdSense client
366
+ */
367
+ static async create(accountIdOverride) {
368
+ const config = await loadConfig();
369
+ let auth;
370
+ if (config.authType === "service-account") {
371
+ auth = await createServiceAccountClient();
372
+ } else {
373
+ auth = await createOAuthClient();
374
+ }
375
+ const adsense = google2.adsense({
376
+ version: "v2",
377
+ auth
378
+ });
379
+ const accountId = accountIdOverride || config.defaultAccountId;
380
+ return new _AdSenseClient(adsense, accountId);
381
+ }
382
+ /**
383
+ * Get the account ID to use (explicit > default > first available)
384
+ */
385
+ async resolveAccountId(explicitId) {
386
+ if (explicitId) {
387
+ return explicitId.startsWith("accounts/") ? explicitId : `accounts/${explicitId}`;
388
+ }
389
+ if (this.defaultAccountId) {
390
+ return this.defaultAccountId.startsWith("accounts/") ? this.defaultAccountId : `accounts/${this.defaultAccountId}`;
391
+ }
392
+ const accounts = await this.listAccounts();
393
+ if (accounts.length === 0) {
394
+ throw new Error("No AdSense accounts found");
395
+ }
396
+ return accounts[0].name;
397
+ }
398
+ /**
399
+ * Extract publisher ID from account name
400
+ */
401
+ extractPubId(accountName) {
402
+ return accountName.replace("accounts/", "");
403
+ }
404
+ // ==================== Account Operations ====================
405
+ /**
406
+ * List all AdSense accounts
407
+ */
408
+ async listAccounts() {
409
+ const cache = getCache();
410
+ const cacheKey = "accounts";
411
+ const cached = cache.get(cacheKey, {});
412
+ if (cached) {
413
+ return cached;
414
+ }
415
+ await rateLimiter.throttle();
416
+ const response = await withRetry(
417
+ () => this.adsense.accounts.list()
418
+ );
419
+ const accounts = response.data.accounts || [];
420
+ cache.set(cacheKey, {}, accounts, CacheTTL.ACCOUNTS, "global");
421
+ return accounts;
422
+ }
423
+ /**
424
+ * Get a specific account
425
+ */
426
+ async getAccount(accountId) {
427
+ const resolvedId = await this.resolveAccountId(accountId);
428
+ await rateLimiter.throttle();
429
+ try {
430
+ const response = await withRetry(
431
+ () => this.adsense.accounts.get({ name: resolvedId })
432
+ );
433
+ return response.data;
434
+ } catch {
435
+ return null;
436
+ }
437
+ }
438
+ // ==================== Sites Operations ====================
439
+ /**
440
+ * List all sites for an account
441
+ */
442
+ async listSites(accountId) {
443
+ const resolvedId = await this.resolveAccountId(accountId);
444
+ const cache = getCache();
445
+ const params = { accountId: resolvedId };
446
+ const cached = cache.get("sites", params);
447
+ if (cached) {
448
+ return cached;
449
+ }
450
+ await rateLimiter.throttle();
451
+ const response = await withRetry(
452
+ () => this.adsense.accounts.sites.list({ parent: resolvedId })
453
+ );
454
+ const sites = response.data.sites || [];
455
+ cache.set("sites", params, sites, CacheTTL.SITES, this.extractPubId(resolvedId));
456
+ return sites;
457
+ }
458
+ // ==================== Alerts Operations ====================
459
+ /**
460
+ * List alerts for an account
461
+ */
462
+ async listAlerts(accountId) {
463
+ const resolvedId = await this.resolveAccountId(accountId);
464
+ const cache = getCache();
465
+ const params = { accountId: resolvedId };
466
+ const cached = cache.get("alerts", params);
467
+ if (cached) {
468
+ return cached;
469
+ }
470
+ await rateLimiter.throttle();
471
+ const response = await withRetry(
472
+ () => this.adsense.accounts.alerts.list({ parent: resolvedId })
473
+ );
474
+ const alerts = response.data.alerts || [];
475
+ cache.set("alerts", params, alerts, CacheTTL.ALERTS, this.extractPubId(resolvedId));
476
+ return alerts;
477
+ }
478
+ // ==================== Policy Issues Operations ====================
479
+ /**
480
+ * List policy issues for an account
481
+ */
482
+ async listPolicyIssues(accountId) {
483
+ const resolvedId = await this.resolveAccountId(accountId);
484
+ const cache = getCache();
485
+ const params = { accountId: resolvedId };
486
+ const cached = cache.get("policyIssues", params);
487
+ if (cached) {
488
+ return cached;
489
+ }
490
+ await rateLimiter.throttle();
491
+ const response = await withRetry(
492
+ () => this.adsense.accounts.policyIssues.list({ parent: resolvedId })
493
+ );
494
+ const issues = response.data.policyIssues || [];
495
+ cache.set("policyIssues", params, issues, CacheTTL.POLICY_ISSUES, this.extractPubId(resolvedId));
496
+ return issues;
497
+ }
498
+ // ==================== Payments Operations ====================
499
+ /**
500
+ * List payments for an account
501
+ */
502
+ async listPayments(accountId) {
503
+ const resolvedId = await this.resolveAccountId(accountId);
504
+ const cache = getCache();
505
+ const params = { accountId: resolvedId };
506
+ const cached = cache.get("payments", params);
507
+ if (cached) {
508
+ return cached;
509
+ }
510
+ await rateLimiter.throttle();
511
+ const response = await withRetry(
512
+ () => this.adsense.accounts.payments.list({ parent: resolvedId })
513
+ );
514
+ const payments = response.data.payments || [];
515
+ cache.set("payments", params, payments, CacheTTL.PAYMENTS, this.extractPubId(resolvedId));
516
+ return payments;
517
+ }
518
+ // ==================== Ad Client Operations ====================
519
+ /**
520
+ * List ad clients for an account
521
+ */
522
+ async listAdClients(accountId) {
523
+ const resolvedId = await this.resolveAccountId(accountId);
524
+ await rateLimiter.throttle();
525
+ const response = await withRetry(
526
+ () => this.adsense.accounts.adclients.list({ parent: resolvedId })
527
+ );
528
+ return response.data.adClients || [];
529
+ }
530
+ // ==================== Ad Unit Operations ====================
531
+ /**
532
+ * List ad units for an account (across all ad clients)
533
+ */
534
+ async listAdUnits(accountId) {
535
+ const resolvedId = await this.resolveAccountId(accountId);
536
+ const cache = getCache();
537
+ const params = { accountId: resolvedId };
538
+ const cached = cache.get("adUnits", params);
539
+ if (cached) {
540
+ return cached;
541
+ }
542
+ const adClients = await this.listAdClients(accountId);
543
+ const allAdUnits = [];
544
+ for (const client of adClients) {
545
+ await rateLimiter.throttle();
546
+ const response = await withRetry(
547
+ () => this.adsense.accounts.adclients.adunits.list({ parent: client.name })
548
+ );
549
+ allAdUnits.push(...response.data.adUnits || []);
550
+ }
551
+ cache.set("adUnits", params, allAdUnits, CacheTTL.AD_UNITS, this.extractPubId(resolvedId));
552
+ return allAdUnits;
553
+ }
554
+ /**
555
+ * Get ad code for an ad unit
556
+ */
557
+ async getAdCode(adClientId, adUnitId, accountId) {
558
+ const resolvedId = await this.resolveAccountId(accountId);
559
+ const adUnitName = `${resolvedId}/adclients/${adClientId}/adunits/${adUnitId}`;
560
+ await rateLimiter.throttle();
561
+ const response = await withRetry(
562
+ () => this.adsense.accounts.adclients.adunits.getAdcode({ name: adUnitName })
563
+ );
564
+ return response.data.adCode || "";
565
+ }
566
+ // ==================== Report Operations ====================
567
+ /**
568
+ * Generate a report
569
+ */
570
+ async generateReport(query) {
571
+ const resolvedId = await this.resolveAccountId(query.accountId);
572
+ const cache = getCache();
573
+ const ttl = this.getReportTTL(query.startDate, query.endDate);
574
+ const cached = cache.get("report", { ...query, accountId: resolvedId });
575
+ if (cached) {
576
+ return cached;
577
+ }
578
+ await rateLimiter.throttle();
579
+ const params = {
580
+ account: resolvedId,
581
+ "dateRange": query.dateRange || "CUSTOM",
582
+ "startDate.year": parseInt(query.startDate.split("-")[0]),
583
+ "startDate.month": parseInt(query.startDate.split("-")[1]),
584
+ "startDate.day": parseInt(query.startDate.split("-")[2]),
585
+ "endDate.year": parseInt(query.endDate.split("-")[0]),
586
+ "endDate.month": parseInt(query.endDate.split("-")[1]),
587
+ "endDate.day": parseInt(query.endDate.split("-")[2])
588
+ };
589
+ if (query.dimensions && query.dimensions.length > 0) {
590
+ params.dimensions = query.dimensions;
591
+ }
592
+ params.metrics = query.metrics || [
593
+ "ESTIMATED_EARNINGS",
594
+ "IMPRESSIONS",
595
+ "CLICKS",
596
+ "PAGE_VIEWS",
597
+ "PAGE_VIEWS_CTR",
598
+ "PAGE_VIEWS_RPM"
599
+ ];
600
+ if (query.orderBy) {
601
+ params.orderBy = query.orderBy.startsWith("-") ? `${query.orderBy.slice(1)} DESC` : `${query.orderBy} ASC`;
602
+ }
603
+ if (query.limit) {
604
+ params.limit = Math.min(query.limit, 1e5);
605
+ }
606
+ const response = await withRetry(
607
+ () => this.adsense.accounts.reports.generate(params)
608
+ );
609
+ const report = response.data;
610
+ cache.set("report", { ...query, accountId: resolvedId }, report, ttl, this.extractPubId(resolvedId));
611
+ return report;
612
+ }
613
+ /**
614
+ * Generate a CSV report
615
+ */
616
+ async generateCsvReport(query) {
617
+ const resolvedId = await this.resolveAccountId(query.accountId);
618
+ await rateLimiter.throttle();
619
+ const params = {
620
+ account: resolvedId,
621
+ "dateRange": query.dateRange || "CUSTOM",
622
+ "startDate.year": parseInt(query.startDate.split("-")[0]),
623
+ "startDate.month": parseInt(query.startDate.split("-")[1]),
624
+ "startDate.day": parseInt(query.startDate.split("-")[2]),
625
+ "endDate.year": parseInt(query.endDate.split("-")[0]),
626
+ "endDate.month": parseInt(query.endDate.split("-")[1]),
627
+ "endDate.day": parseInt(query.endDate.split("-")[2])
628
+ };
629
+ if (query.dimensions) {
630
+ params.dimensions = query.dimensions;
631
+ }
632
+ params.metrics = query.metrics || [
633
+ "ESTIMATED_EARNINGS",
634
+ "IMPRESSIONS",
635
+ "CLICKS",
636
+ "PAGE_VIEWS"
637
+ ];
638
+ const response = await withRetry(
639
+ () => this.adsense.accounts.reports.generateCsv(params)
640
+ );
641
+ return response.data;
642
+ }
643
+ /**
644
+ * Get earnings summary (today, yesterday, last 7 days, this month, last month)
645
+ */
646
+ async getEarningsSummary(accountId) {
647
+ const resolvedId = await this.resolveAccountId(accountId);
648
+ const today = /* @__PURE__ */ new Date();
649
+ const yesterday = new Date(today);
650
+ yesterday.setDate(yesterday.getDate() - 1);
651
+ const formatDate = (d) => d.toISOString().split("T")[0];
652
+ const todayStr = formatDate(today);
653
+ const yesterdayStr = formatDate(yesterday);
654
+ const last7DaysStart = new Date(today);
655
+ last7DaysStart.setDate(last7DaysStart.getDate() - 6);
656
+ const thisMonthStart = new Date(today.getFullYear(), today.getMonth(), 1);
657
+ const lastMonthStart = new Date(today.getFullYear(), today.getMonth() - 1, 1);
658
+ const lastMonthEnd = new Date(today.getFullYear(), today.getMonth(), 0);
659
+ const [todayReport, yesterdayReport, last7DaysReport, thisMonthReport, lastMonthReport] = await Promise.all([
660
+ this.generateReport({
661
+ accountId: resolvedId,
662
+ startDate: todayStr,
663
+ endDate: todayStr
664
+ }),
665
+ this.generateReport({
666
+ accountId: resolvedId,
667
+ startDate: yesterdayStr,
668
+ endDate: yesterdayStr
669
+ }),
670
+ this.generateReport({
671
+ accountId: resolvedId,
672
+ startDate: formatDate(last7DaysStart),
673
+ endDate: todayStr
674
+ }),
675
+ this.generateReport({
676
+ accountId: resolvedId,
677
+ startDate: formatDate(thisMonthStart),
678
+ endDate: todayStr
679
+ }),
680
+ this.generateReport({
681
+ accountId: resolvedId,
682
+ startDate: formatDate(lastMonthStart),
683
+ endDate: formatDate(lastMonthEnd)
684
+ })
685
+ ]);
686
+ return {
687
+ today: this.extractEarningsPeriod(todayReport),
688
+ yesterday: this.extractEarningsPeriod(yesterdayReport),
689
+ last7Days: this.extractEarningsPeriod(last7DaysReport),
690
+ thisMonth: this.extractEarningsPeriod(thisMonthReport),
691
+ lastMonth: this.extractEarningsPeriod(lastMonthReport)
692
+ };
693
+ }
694
+ /**
695
+ * Extract earnings data from a report response
696
+ */
697
+ extractEarningsPeriod(report) {
698
+ const totals = report.totals?.cells || report.rows?.[0]?.cells || [];
699
+ const headers = report.headers || [];
700
+ const getValue = (metricName) => {
701
+ const index = headers.findIndex((h) => h.name === metricName);
702
+ if (index >= 0 && totals[index]) {
703
+ return parseFloat(totals[index].value) || 0;
704
+ }
705
+ return 0;
706
+ };
707
+ return {
708
+ earnings: getValue("ESTIMATED_EARNINGS"),
709
+ impressions: getValue("IMPRESSIONS"),
710
+ clicks: getValue("CLICKS"),
711
+ ctr: getValue("PAGE_VIEWS_CTR") || getValue("IMPRESSIONS_CTR"),
712
+ rpm: getValue("PAGE_VIEWS_RPM") || getValue("IMPRESSIONS_RPM"),
713
+ pageViews: getValue("PAGE_VIEWS")
714
+ };
715
+ }
716
+ /**
717
+ * Determine appropriate cache TTL based on date range
718
+ */
719
+ getReportTTL(startDate, endDate) {
720
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
721
+ const yesterday = /* @__PURE__ */ new Date();
722
+ yesterday.setDate(yesterday.getDate() - 1);
723
+ const yesterdayStr = yesterday.toISOString().split("T")[0];
724
+ if (endDate === today || startDate === today) {
725
+ return CacheTTL.TODAY_EARNINGS;
726
+ }
727
+ if (endDate === yesterdayStr) {
728
+ return CacheTTL.YESTERDAY_EARNINGS;
729
+ }
730
+ return CacheTTL.HISTORICAL_REPORT;
731
+ }
732
+ };
733
+
734
+ // src/server/tools/accounts.ts
735
+ async function handleListAccounts() {
736
+ const client = await AdSenseClient.create();
737
+ const accounts = await client.listAccounts();
738
+ return {
739
+ accounts: accounts.map((account) => ({
740
+ id: account.name.replace("accounts/", ""),
741
+ name: account.name,
742
+ displayName: account.displayName || "Unnamed Account",
743
+ timeZone: account.timeZone?.id || "Unknown",
744
+ createTime: account.createTime,
745
+ premium: account.premium || false
746
+ })),
747
+ total: accounts.length
748
+ };
749
+ }
750
+
751
+ // src/server/tools/reports.ts
752
+ async function handleEarningsSummary(args) {
753
+ const accountId = args.accountId || process.env.ADSENSE_ACCOUNT_ID;
754
+ const client = await AdSenseClient.create(accountId);
755
+ const summary = await client.getEarningsSummary(accountId);
756
+ const formatCurrency = (value) => `$${value.toFixed(2)}`;
757
+ const formatNumber = (value) => value.toLocaleString();
758
+ const formatPercent = (value) => `${(value * 100).toFixed(2)}%`;
759
+ return {
760
+ summary: {
761
+ today: {
762
+ earnings: formatCurrency(summary.today.earnings),
763
+ impressions: formatNumber(summary.today.impressions),
764
+ clicks: formatNumber(summary.today.clicks),
765
+ ctr: formatPercent(summary.today.ctr),
766
+ rpm: formatCurrency(summary.today.rpm),
767
+ pageViews: formatNumber(summary.today.pageViews)
768
+ },
769
+ yesterday: {
770
+ earnings: formatCurrency(summary.yesterday.earnings),
771
+ impressions: formatNumber(summary.yesterday.impressions),
772
+ clicks: formatNumber(summary.yesterday.clicks),
773
+ ctr: formatPercent(summary.yesterday.ctr),
774
+ rpm: formatCurrency(summary.yesterday.rpm),
775
+ pageViews: formatNumber(summary.yesterday.pageViews)
776
+ },
777
+ last7Days: {
778
+ earnings: formatCurrency(summary.last7Days.earnings),
779
+ impressions: formatNumber(summary.last7Days.impressions),
780
+ clicks: formatNumber(summary.last7Days.clicks),
781
+ ctr: formatPercent(summary.last7Days.ctr),
782
+ rpm: formatCurrency(summary.last7Days.rpm),
783
+ pageViews: formatNumber(summary.last7Days.pageViews)
784
+ },
785
+ thisMonth: {
786
+ earnings: formatCurrency(summary.thisMonth.earnings),
787
+ impressions: formatNumber(summary.thisMonth.impressions),
788
+ clicks: formatNumber(summary.thisMonth.clicks),
789
+ ctr: formatPercent(summary.thisMonth.ctr),
790
+ rpm: formatCurrency(summary.thisMonth.rpm),
791
+ pageViews: formatNumber(summary.thisMonth.pageViews)
792
+ },
793
+ lastMonth: {
794
+ earnings: formatCurrency(summary.lastMonth.earnings),
795
+ impressions: formatNumber(summary.lastMonth.impressions),
796
+ clicks: formatNumber(summary.lastMonth.clicks),
797
+ ctr: formatPercent(summary.lastMonth.ctr),
798
+ rpm: formatCurrency(summary.lastMonth.rpm),
799
+ pageViews: formatNumber(summary.lastMonth.pageViews)
800
+ }
801
+ },
802
+ raw: summary,
803
+ comparison: {
804
+ vsYesterday: summary.yesterday.earnings > 0 ? ((summary.today.earnings - summary.yesterday.earnings) / summary.yesterday.earnings * 100).toFixed(1) + "%" : "N/A",
805
+ vsLastMonth: summary.lastMonth.earnings > 0 ? ((summary.thisMonth.earnings - summary.lastMonth.earnings) / summary.lastMonth.earnings * 100).toFixed(1) + "%" : "N/A"
806
+ }
807
+ };
808
+ }
809
+ async function handleGenerateReport(args) {
810
+ const accountId = args.accountId || process.env.ADSENSE_ACCOUNT_ID;
811
+ const client = await AdSenseClient.create(accountId);
812
+ const endDate = args.endDate || getYesterday();
813
+ const startDate = args.startDate || get7DaysAgo();
814
+ const report = await client.generateReport({
815
+ accountId: accountId || "",
816
+ startDate,
817
+ endDate,
818
+ dimensions: args.dimensions || void 0,
819
+ metrics: args.metrics || void 0,
820
+ orderBy: args.orderBy || void 0,
821
+ limit: args.limit || 100
822
+ });
823
+ const headers = report.headers?.map((h) => h.name) || [];
824
+ const rows = report.rows?.map(
825
+ (row) => row.cells.reduce((acc, cell, i) => {
826
+ acc[headers[i]] = cell.value;
827
+ return acc;
828
+ }, {})
829
+ ) || [];
830
+ return {
831
+ dateRange: {
832
+ start: startDate,
833
+ end: endDate
834
+ },
835
+ headers,
836
+ rows,
837
+ totals: report.totals?.cells.reduce((acc, cell, i) => {
838
+ acc[headers[i]] = cell.value;
839
+ return acc;
840
+ }, {}),
841
+ totalRows: report.totalMatchedRows || rows.length.toString()
842
+ };
843
+ }
844
+ async function handleComparePeriods(args) {
845
+ const accountId = args.accountId || process.env.ADSENSE_ACCOUNT_ID;
846
+ const client = await AdSenseClient.create(accountId);
847
+ const period1Start = args.period1Start;
848
+ const period1End = args.period1End;
849
+ const period2Start = args.period2Start;
850
+ const period2End = args.period2End;
851
+ if (!period1Start || !period1End || !period2Start || !period2End) {
852
+ throw new Error("All period dates are required");
853
+ }
854
+ const dimensions = args.dimensions || void 0;
855
+ const [report1, report2] = await Promise.all([
856
+ client.generateReport({
857
+ accountId: accountId || "",
858
+ startDate: period1Start,
859
+ endDate: period1End,
860
+ dimensions
861
+ }),
862
+ client.generateReport({
863
+ accountId: accountId || "",
864
+ startDate: period2Start,
865
+ endDate: period2End,
866
+ dimensions
867
+ })
868
+ ]);
869
+ const extractTotals = (report) => {
870
+ const headers = report.headers?.map((h) => h.name) || [];
871
+ const totals = report.totals?.cells || report.rows?.[0]?.cells || [];
872
+ return headers.reduce((acc, header, i) => {
873
+ acc[header] = parseFloat(totals[i]?.value) || 0;
874
+ return acc;
875
+ }, {});
876
+ };
877
+ const period1Totals = extractTotals(report1);
878
+ const period2Totals = extractTotals(report2);
879
+ const changes = {};
880
+ for (const key of Object.keys(period1Totals)) {
881
+ const val1 = period1Totals[key];
882
+ const val2 = period2Totals[key] || 0;
883
+ const diff = val1 - val2;
884
+ const percent = val2 !== 0 ? diff / val2 * 100 : 0;
885
+ changes[key] = {
886
+ period1: val1,
887
+ period2: val2,
888
+ change: diff >= 0 ? `+${diff.toFixed(2)}` : diff.toFixed(2),
889
+ changePercent: percent >= 0 ? `+${percent.toFixed(1)}%` : `${percent.toFixed(1)}%`
890
+ };
891
+ }
892
+ return {
893
+ period1: {
894
+ start: period1Start,
895
+ end: period1End,
896
+ totals: period1Totals
897
+ },
898
+ period2: {
899
+ start: period2Start,
900
+ end: period2End,
901
+ totals: period2Totals
902
+ },
903
+ changes
904
+ };
905
+ }
906
+ function getYesterday() {
907
+ const d = /* @__PURE__ */ new Date();
908
+ d.setDate(d.getDate() - 1);
909
+ return d.toISOString().split("T")[0];
910
+ }
911
+ function get7DaysAgo() {
912
+ const d = /* @__PURE__ */ new Date();
913
+ d.setDate(d.getDate() - 7);
914
+ return d.toISOString().split("T")[0];
915
+ }
916
+
917
+ // src/server/tools/sites.ts
918
+ async function handleListSites(args) {
919
+ const accountId = args.accountId || process.env.ADSENSE_ACCOUNT_ID;
920
+ const client = await AdSenseClient.create(accountId);
921
+ const sites = await client.listSites(accountId);
922
+ const byStatus = {
923
+ ready: sites.filter((s) => s.state === "READY"),
924
+ gettingReady: sites.filter((s) => s.state === "GETTING_READY"),
925
+ needsAttention: sites.filter((s) => s.state === "NEEDS_ATTENTION"),
926
+ requiresReview: sites.filter((s) => s.state === "REQUIRES_REVIEW")
927
+ };
928
+ const formatSite = (site) => {
929
+ const statusEmojiMap = {
930
+ "READY": "\u2705",
931
+ "GETTING_READY": "\u23F3",
932
+ "NEEDS_ATTENTION": "\u26A0\uFE0F",
933
+ "REQUIRES_REVIEW": "\u{1F4DD}",
934
+ "STATE_UNSPECIFIED": "\u2753"
935
+ };
936
+ const statusEmoji = statusEmojiMap[site.state] || "\u2753";
937
+ return {
938
+ domain: site.domain,
939
+ status: site.state,
940
+ statusEmoji,
941
+ autoAdsEnabled: site.autoAdsEnabled || false,
942
+ name: site.name
943
+ };
944
+ };
945
+ return {
946
+ sites: sites.map(formatSite),
947
+ summary: {
948
+ total: sites.length,
949
+ ready: byStatus.ready.length,
950
+ gettingReady: byStatus.gettingReady.length,
951
+ needsAttention: byStatus.needsAttention.length,
952
+ requiresReview: byStatus.requiresReview.length
953
+ },
954
+ statusDescriptions: {
955
+ READY: "Approved and serving ads",
956
+ GETTING_READY: "Under review by Google (may take 1-2 weeks)",
957
+ NEEDS_ATTENTION: "Issues to fix before approval",
958
+ REQUIRES_REVIEW: "Site needs review or is inactive"
959
+ }
960
+ };
961
+ }
962
+
963
+ // src/server/tools/alerts.ts
964
+ async function handleListAlerts(args) {
965
+ const accountId = args.accountId || process.env.ADSENSE_ACCOUNT_ID;
966
+ const client = await AdSenseClient.create(accountId);
967
+ const alerts = await client.listAlerts(accountId);
968
+ const bySeverity = {
969
+ severe: alerts.filter((a) => a.severity === "SEVERE"),
970
+ warning: alerts.filter((a) => a.severity === "WARNING"),
971
+ info: alerts.filter((a) => a.severity === "INFO")
972
+ };
973
+ const formatAlert = (alert) => {
974
+ const severityEmojiMap = {
975
+ "SEVERE": "\u{1F534}",
976
+ "WARNING": "\u{1F7E1}",
977
+ "INFO": "\u{1F535}",
978
+ "SEVERITY_UNSPECIFIED": "\u26AA"
979
+ };
980
+ const severityEmoji = severityEmojiMap[alert.severity] || "\u26AA";
981
+ return {
982
+ message: alert.message,
983
+ severity: alert.severity,
984
+ severityEmoji,
985
+ type: alert.type,
986
+ name: alert.name
987
+ };
988
+ };
989
+ return {
990
+ alerts: alerts.map(formatAlert),
991
+ summary: {
992
+ total: alerts.length,
993
+ severe: bySeverity.severe.length,
994
+ warning: bySeverity.warning.length,
995
+ info: bySeverity.info.length
996
+ },
997
+ hasIssues: bySeverity.severe.length > 0 || bySeverity.warning.length > 0,
998
+ severityDescriptions: {
999
+ SEVERE: "Immediate action required (payment holds, violations)",
1000
+ WARNING: "Action recommended",
1001
+ INFO: "General notifications"
1002
+ }
1003
+ };
1004
+ }
1005
+ async function handleListPolicyIssues(args) {
1006
+ const accountId = args.accountId || process.env.ADSENSE_ACCOUNT_ID;
1007
+ const client = await AdSenseClient.create(accountId);
1008
+ const issues = await client.listPolicyIssues(accountId);
1009
+ const formatIssue = (issue) => {
1010
+ const actionEmojiMap = {
1011
+ "WARNED": "\u26A0\uFE0F",
1012
+ "AD_SERVING_RESTRICTED": "\u{1F536}",
1013
+ "AD_SERVING_DISABLED": "\u{1F534}",
1014
+ "AD_SERVED_WITH_CLICK_CONFIRMATION": "\u{1F7E1}",
1015
+ "AD_PERSONALIZATION_RESTRICTED": "\u{1F7E0}",
1016
+ "ENFORCEMENT_ACTION_UNSPECIFIED": "\u26AA"
1017
+ };
1018
+ const actionEmoji = actionEmojiMap[issue.action] || "\u26AA";
1019
+ return {
1020
+ site: issue.site,
1021
+ siteSection: issue.siteSection,
1022
+ uri: issue.uri,
1023
+ entityType: issue.entityType,
1024
+ action: issue.action,
1025
+ actionEmoji,
1026
+ adRequestCount: issue.adRequestCount,
1027
+ warningEscalationDate: issue.warningEscalationDate ? `${issue.warningEscalationDate.year}-${String(issue.warningEscalationDate.month).padStart(2, "0")}-${String(issue.warningEscalationDate.day).padStart(2, "0")}` : null,
1028
+ name: issue.name
1029
+ };
1030
+ };
1031
+ const byAction = {
1032
+ warned: issues.filter((i) => i.action === "WARNED"),
1033
+ restricted: issues.filter((i) => i.action === "AD_SERVING_RESTRICTED"),
1034
+ disabled: issues.filter((i) => i.action === "AD_SERVING_DISABLED"),
1035
+ clickConfirmation: issues.filter((i) => i.action === "AD_SERVED_WITH_CLICK_CONFIRMATION"),
1036
+ personalizationRestricted: issues.filter((i) => i.action === "AD_PERSONALIZATION_RESTRICTED")
1037
+ };
1038
+ return {
1039
+ issues: issues.map(formatIssue),
1040
+ summary: {
1041
+ total: issues.length,
1042
+ warned: byAction.warned.length,
1043
+ restricted: byAction.restricted.length,
1044
+ disabled: byAction.disabled.length,
1045
+ clickConfirmation: byAction.clickConfirmation.length,
1046
+ personalizationRestricted: byAction.personalizationRestricted.length
1047
+ },
1048
+ hasIssues: issues.length > 0,
1049
+ actionDescriptions: {
1050
+ WARNED: "Pending enforcement with deadline - fix before escalation",
1051
+ AD_SERVING_RESTRICTED: "Reduced ad demand on affected pages",
1052
+ AD_SERVING_DISABLED: "Ads completely stopped on affected pages",
1053
+ AD_SERVED_WITH_CLICK_CONFIRMATION: "Extra click verification required",
1054
+ AD_PERSONALIZATION_RESTRICTED: "Limited to basic (non-personalized) ads"
1055
+ }
1056
+ };
1057
+ }
1058
+
1059
+ // src/server/tools/payments.ts
1060
+ async function handleListPayments(args) {
1061
+ const accountId = args.accountId || process.env.ADSENSE_ACCOUNT_ID;
1062
+ const client = await AdSenseClient.create(accountId);
1063
+ const payments = await client.listPayments(accountId);
1064
+ const formatPayment = (payment) => {
1065
+ const date = payment.date ? `${payment.date.year}-${String(payment.date.month).padStart(2, "0")}-${String(payment.date.day).padStart(2, "0")}` : null;
1066
+ const name = payment.name || "";
1067
+ let type = "payment";
1068
+ if (name.includes("unpaid")) {
1069
+ type = "unpaid";
1070
+ } else if (name.includes("youtube")) {
1071
+ type = "youtube";
1072
+ }
1073
+ return {
1074
+ amount: payment.amount || "N/A",
1075
+ date,
1076
+ type,
1077
+ name: payment.name
1078
+ };
1079
+ };
1080
+ const unpaid = payments.filter((p) => p.name?.includes("unpaid"));
1081
+ const paid = payments.filter((p) => !p.name?.includes("unpaid"));
1082
+ const totalPaid = paid.reduce((sum, p) => {
1083
+ const match = p.amount?.match(/[\d.]+/);
1084
+ return sum + (match ? parseFloat(match[0]) : 0);
1085
+ }, 0);
1086
+ return {
1087
+ payments: payments.map(formatPayment),
1088
+ summary: {
1089
+ totalPayments: paid.length,
1090
+ unpaidBalance: unpaid.length > 0 ? unpaid[0].amount : "N/A",
1091
+ totalPaidAllTime: `$${totalPaid.toFixed(2)}`
1092
+ },
1093
+ unpaid: unpaid.map(formatPayment),
1094
+ paid: paid.map(formatPayment)
1095
+ };
1096
+ }
1097
+
1098
+ // src/server/tools/adUnits.ts
1099
+ async function handleListAdUnits(args) {
1100
+ const accountId = args.accountId || process.env.ADSENSE_ACCOUNT_ID;
1101
+ const client = await AdSenseClient.create(accountId);
1102
+ const adUnits = await client.listAdUnits(accountId);
1103
+ const formatAdUnit = (unit) => {
1104
+ const stateEmojiMap = {
1105
+ "ACTIVE": "\u2705",
1106
+ "ARCHIVED": "\u{1F4E6}",
1107
+ "STATE_UNSPECIFIED": "\u2753"
1108
+ };
1109
+ const stateEmoji = stateEmojiMap[unit.state] || "\u2753";
1110
+ const nameParts = unit.name.split("/");
1111
+ const adClientId = nameParts[3] || "";
1112
+ const adUnitId = nameParts[5] || "";
1113
+ return {
1114
+ displayName: unit.displayName,
1115
+ state: unit.state,
1116
+ stateEmoji,
1117
+ type: unit.contentAdsSettings?.type || "UNKNOWN",
1118
+ size: unit.contentAdsSettings?.size || "Auto",
1119
+ adClientId,
1120
+ adUnitId,
1121
+ name: unit.name,
1122
+ reportingDimensionId: unit.reportingDimensionId
1123
+ };
1124
+ };
1125
+ const byState = {
1126
+ active: adUnits.filter((u) => u.state === "ACTIVE"),
1127
+ archived: adUnits.filter((u) => u.state === "ARCHIVED")
1128
+ };
1129
+ const byType = {};
1130
+ for (const unit of adUnits) {
1131
+ const type = unit.contentAdsSettings?.type || "UNKNOWN";
1132
+ if (!byType[type]) {
1133
+ byType[type] = [];
1134
+ }
1135
+ byType[type].push(unit);
1136
+ }
1137
+ return {
1138
+ adUnits: adUnits.map(formatAdUnit),
1139
+ summary: {
1140
+ total: adUnits.length,
1141
+ active: byState.active.length,
1142
+ archived: byState.archived.length,
1143
+ byType: Object.fromEntries(
1144
+ Object.entries(byType).map(([type, units]) => [type, units.length])
1145
+ )
1146
+ },
1147
+ typeDescriptions: {
1148
+ DISPLAY: "Standard display ads (image, text, rich media)",
1149
+ FEED: "In-feed ads that match your content style",
1150
+ ARTICLE: "In-article ads for between paragraphs",
1151
+ MATCHED_CONTENT: "Content recommendations with ads",
1152
+ LINK: "Contextual link unit ads"
1153
+ }
1154
+ };
1155
+ }
1156
+ async function handleGetAdCode(args) {
1157
+ const accountId = args.accountId || process.env.ADSENSE_ACCOUNT_ID;
1158
+ const adClientId = args.adClientId;
1159
+ const adUnitId = args.adUnitId;
1160
+ if (!adClientId || !adUnitId) {
1161
+ throw new Error("adClientId and adUnitId are required");
1162
+ }
1163
+ const client = await AdSenseClient.create(accountId);
1164
+ const adCode = await client.getAdCode(adClientId, adUnitId, accountId);
1165
+ return {
1166
+ adCode,
1167
+ adClientId,
1168
+ adUnitId,
1169
+ instructions: [
1170
+ "Copy the ad code above",
1171
+ "Paste it into your website HTML where you want the ad to appear",
1172
+ "The ad code should be placed within the <body> tags",
1173
+ "Make sure to test the ad placement before publishing"
1174
+ ]
1175
+ };
1176
+ }
1177
+
1178
+ // src/server/tools/export.ts
1179
+ async function handleExportCsv(args) {
1180
+ const accountId = args.accountId || process.env.ADSENSE_ACCOUNT_ID;
1181
+ const startDate = args.startDate;
1182
+ const endDate = args.endDate;
1183
+ if (!startDate || !endDate) {
1184
+ throw new Error("startDate and endDate are required");
1185
+ }
1186
+ const client = await AdSenseClient.create(accountId);
1187
+ try {
1188
+ const csvData = await client.generateCsvReport({
1189
+ accountId: accountId || "",
1190
+ startDate,
1191
+ endDate,
1192
+ dimensions: args.dimensions || void 0,
1193
+ metrics: args.metrics || void 0
1194
+ });
1195
+ return {
1196
+ format: "csv",
1197
+ dateRange: { start: startDate, end: endDate },
1198
+ data: csvData
1199
+ };
1200
+ } catch {
1201
+ const report = await client.generateReport({
1202
+ accountId: accountId || "",
1203
+ startDate,
1204
+ endDate,
1205
+ dimensions: args.dimensions || void 0,
1206
+ metrics: args.metrics || void 0,
1207
+ limit: 1e4
1208
+ });
1209
+ const headers = report.headers?.map((h) => h.name) || [];
1210
+ const rows = report.rows || [];
1211
+ const csvLines = [
1212
+ headers.join(","),
1213
+ ...rows.map(
1214
+ (row) => row.cells.map((cell) => {
1215
+ const value = cell.value;
1216
+ if (value.includes(",") || value.includes('"')) {
1217
+ return `"${value.replace(/"/g, '""')}"`;
1218
+ }
1219
+ return value;
1220
+ }).join(",")
1221
+ )
1222
+ ];
1223
+ return {
1224
+ format: "csv",
1225
+ dateRange: { start: startDate, end: endDate },
1226
+ data: csvLines.join("\n"),
1227
+ rowCount: rows.length
1228
+ };
1229
+ }
1230
+ }
1231
+
1232
+ // src/server/tools/index.ts
1233
+ function registerTools() {
1234
+ return [
1235
+ // Account tools
1236
+ {
1237
+ name: "adsense_list_accounts",
1238
+ description: "List all AdSense accounts you have access to",
1239
+ inputSchema: {
1240
+ type: "object",
1241
+ properties: {},
1242
+ required: []
1243
+ }
1244
+ },
1245
+ // Report tools
1246
+ {
1247
+ name: "adsense_earnings_summary",
1248
+ description: "Get a quick earnings summary (today, yesterday, last 7 days, this month, last month)",
1249
+ inputSchema: {
1250
+ type: "object",
1251
+ properties: {
1252
+ accountId: {
1253
+ type: "string",
1254
+ description: "AdSense account ID (e.g., pub-1234567890123456). Uses default if not specified."
1255
+ }
1256
+ },
1257
+ required: []
1258
+ }
1259
+ },
1260
+ {
1261
+ name: "adsense_generate_report",
1262
+ description: "Generate a detailed AdSense performance report with custom dimensions and metrics",
1263
+ inputSchema: {
1264
+ type: "object",
1265
+ properties: {
1266
+ accountId: {
1267
+ type: "string",
1268
+ description: "AdSense account ID. Uses default if not specified."
1269
+ },
1270
+ startDate: {
1271
+ type: "string",
1272
+ description: "Start date (YYYY-MM-DD). Defaults to 7 days ago."
1273
+ },
1274
+ endDate: {
1275
+ type: "string",
1276
+ description: "End date (YYYY-MM-DD). Defaults to yesterday."
1277
+ },
1278
+ dimensions: {
1279
+ type: "array",
1280
+ items: { type: "string" },
1281
+ description: "Dimensions to group by: DATE, WEEK, MONTH, DOMAIN_NAME, PAGE_URL, AD_UNIT_NAME, COUNTRY_NAME, PLATFORM_TYPE_CODE, TRAFFIC_SOURCE_CODE"
1282
+ },
1283
+ metrics: {
1284
+ type: "array",
1285
+ items: { type: "string" },
1286
+ description: "Metrics to include: ESTIMATED_EARNINGS, IMPRESSIONS, CLICKS, PAGE_VIEWS, PAGE_VIEWS_CTR, PAGE_VIEWS_RPM, AD_REQUESTS, AD_REQUESTS_COVERAGE"
1287
+ },
1288
+ orderBy: {
1289
+ type: "string",
1290
+ description: "Metric to sort by (prefix with - for descending, e.g., -ESTIMATED_EARNINGS)"
1291
+ },
1292
+ limit: {
1293
+ type: "number",
1294
+ description: "Max rows to return (default: 100, max: 10000)"
1295
+ }
1296
+ },
1297
+ required: []
1298
+ }
1299
+ },
1300
+ {
1301
+ name: "adsense_compare_periods",
1302
+ description: "Compare AdSense performance between two time periods",
1303
+ inputSchema: {
1304
+ type: "object",
1305
+ properties: {
1306
+ accountId: {
1307
+ type: "string",
1308
+ description: "AdSense account ID. Uses default if not specified."
1309
+ },
1310
+ period1Start: {
1311
+ type: "string",
1312
+ description: "First period start date (YYYY-MM-DD)"
1313
+ },
1314
+ period1End: {
1315
+ type: "string",
1316
+ description: "First period end date (YYYY-MM-DD)"
1317
+ },
1318
+ period2Start: {
1319
+ type: "string",
1320
+ description: "Second period start date (YYYY-MM-DD)"
1321
+ },
1322
+ period2End: {
1323
+ type: "string",
1324
+ description: "Second period end date (YYYY-MM-DD)"
1325
+ },
1326
+ dimensions: {
1327
+ type: "array",
1328
+ items: { type: "string" },
1329
+ description: "Dimensions to group by for comparison"
1330
+ }
1331
+ },
1332
+ required: ["period1Start", "period1End", "period2Start", "period2End"]
1333
+ }
1334
+ },
1335
+ // Sites tools
1336
+ {
1337
+ name: "adsense_list_sites",
1338
+ description: "List all sites and their AdSense approval status (READY, GETTING_READY, NEEDS_ATTENTION, REQUIRES_REVIEW)",
1339
+ inputSchema: {
1340
+ type: "object",
1341
+ properties: {
1342
+ accountId: {
1343
+ type: "string",
1344
+ description: "AdSense account ID. Uses default if not specified."
1345
+ }
1346
+ },
1347
+ required: []
1348
+ }
1349
+ },
1350
+ // Alerts tools
1351
+ {
1352
+ name: "adsense_list_alerts",
1353
+ description: "List AdSense account alerts and warnings (INFO, WARNING, SEVERE)",
1354
+ inputSchema: {
1355
+ type: "object",
1356
+ properties: {
1357
+ accountId: {
1358
+ type: "string",
1359
+ description: "AdSense account ID. Uses default if not specified."
1360
+ }
1361
+ },
1362
+ required: []
1363
+ }
1364
+ },
1365
+ {
1366
+ name: "adsense_list_policy_issues",
1367
+ description: "List policy issues and violations affecting your account (WARNED, AD_SERVING_RESTRICTED, AD_SERVING_DISABLED)",
1368
+ inputSchema: {
1369
+ type: "object",
1370
+ properties: {
1371
+ accountId: {
1372
+ type: "string",
1373
+ description: "AdSense account ID. Uses default if not specified."
1374
+ }
1375
+ },
1376
+ required: []
1377
+ }
1378
+ },
1379
+ // Payments tools
1380
+ {
1381
+ name: "adsense_list_payments",
1382
+ description: "List payment history and pending earnings",
1383
+ inputSchema: {
1384
+ type: "object",
1385
+ properties: {
1386
+ accountId: {
1387
+ type: "string",
1388
+ description: "AdSense account ID. Uses default if not specified."
1389
+ }
1390
+ },
1391
+ required: []
1392
+ }
1393
+ },
1394
+ // Ad units tools
1395
+ {
1396
+ name: "adsense_list_ad_units",
1397
+ description: "List all ad units across your ad clients",
1398
+ inputSchema: {
1399
+ type: "object",
1400
+ properties: {
1401
+ accountId: {
1402
+ type: "string",
1403
+ description: "AdSense account ID. Uses default if not specified."
1404
+ }
1405
+ },
1406
+ required: []
1407
+ }
1408
+ },
1409
+ {
1410
+ name: "adsense_get_ad_code",
1411
+ description: "Get the HTML embed code for an ad unit",
1412
+ inputSchema: {
1413
+ type: "object",
1414
+ properties: {
1415
+ accountId: {
1416
+ type: "string",
1417
+ description: "AdSense account ID. Uses default if not specified."
1418
+ },
1419
+ adClientId: {
1420
+ type: "string",
1421
+ description: "Ad client ID (e.g., ca-pub-1234567890123456)"
1422
+ },
1423
+ adUnitId: {
1424
+ type: "string",
1425
+ description: "Ad unit ID"
1426
+ }
1427
+ },
1428
+ required: ["adClientId", "adUnitId"]
1429
+ }
1430
+ },
1431
+ // Export tools
1432
+ {
1433
+ name: "adsense_export_csv",
1434
+ description: "Export AdSense report data as CSV format",
1435
+ inputSchema: {
1436
+ type: "object",
1437
+ properties: {
1438
+ accountId: {
1439
+ type: "string",
1440
+ description: "AdSense account ID. Uses default if not specified."
1441
+ },
1442
+ startDate: {
1443
+ type: "string",
1444
+ description: "Start date (YYYY-MM-DD)"
1445
+ },
1446
+ endDate: {
1447
+ type: "string",
1448
+ description: "End date (YYYY-MM-DD)"
1449
+ },
1450
+ dimensions: {
1451
+ type: "array",
1452
+ items: { type: "string" },
1453
+ description: "Dimensions to include"
1454
+ },
1455
+ metrics: {
1456
+ type: "array",
1457
+ items: { type: "string" },
1458
+ description: "Metrics to include"
1459
+ }
1460
+ },
1461
+ required: ["startDate", "endDate"]
1462
+ }
1463
+ }
1464
+ ];
1465
+ }
1466
+ async function handleToolCall(name, args) {
1467
+ try {
1468
+ let result;
1469
+ switch (name) {
1470
+ case "adsense_list_accounts":
1471
+ result = await handleListAccounts();
1472
+ break;
1473
+ case "adsense_earnings_summary":
1474
+ result = await handleEarningsSummary(args);
1475
+ break;
1476
+ case "adsense_generate_report":
1477
+ result = await handleGenerateReport(args);
1478
+ break;
1479
+ case "adsense_compare_periods":
1480
+ result = await handleComparePeriods(args);
1481
+ break;
1482
+ case "adsense_list_sites":
1483
+ result = await handleListSites(args);
1484
+ break;
1485
+ case "adsense_list_alerts":
1486
+ result = await handleListAlerts(args);
1487
+ break;
1488
+ case "adsense_list_policy_issues":
1489
+ result = await handleListPolicyIssues(args);
1490
+ break;
1491
+ case "adsense_list_payments":
1492
+ result = await handleListPayments(args);
1493
+ break;
1494
+ case "adsense_list_ad_units":
1495
+ result = await handleListAdUnits(args);
1496
+ break;
1497
+ case "adsense_get_ad_code":
1498
+ result = await handleGetAdCode(args);
1499
+ break;
1500
+ case "adsense_export_csv":
1501
+ result = await handleExportCsv(args);
1502
+ break;
1503
+ default:
1504
+ throw new Error(`Unknown tool: ${name}`);
1505
+ }
1506
+ return {
1507
+ content: [
1508
+ {
1509
+ type: "text",
1510
+ text: typeof result === "string" ? result : JSON.stringify(result, null, 2)
1511
+ }
1512
+ ]
1513
+ };
1514
+ } catch (error) {
1515
+ return {
1516
+ content: [
1517
+ {
1518
+ type: "text",
1519
+ text: `Error: ${error.message}`
1520
+ }
1521
+ ]
1522
+ };
1523
+ }
1524
+ }
1525
+
1526
+ // src/server/resources/index.ts
1527
+ async function handleListResources() {
1528
+ return {
1529
+ resources: [
1530
+ {
1531
+ uri: "adsense://accounts",
1532
+ name: "AdSense Accounts",
1533
+ description: "List of all AdSense accounts you have access to",
1534
+ mimeType: "application/json"
1535
+ }
1536
+ ]
1537
+ };
1538
+ }
1539
+ async function handleReadResource(uri) {
1540
+ const url = new URL(uri);
1541
+ if (url.protocol !== "adsense:") {
1542
+ throw new Error(`Unsupported protocol: ${url.protocol}`);
1543
+ }
1544
+ const path2 = url.pathname.replace(/^\/\//, "");
1545
+ if (path2 === "accounts") {
1546
+ const client = await AdSenseClient.create();
1547
+ const accounts = await client.listAccounts();
1548
+ return {
1549
+ contents: [
1550
+ {
1551
+ uri,
1552
+ mimeType: "application/json",
1553
+ text: JSON.stringify(accounts, null, 2)
1554
+ }
1555
+ ]
1556
+ };
1557
+ }
1558
+ const sitesMatch = path2.match(/^accounts\/([^/]+)\/sites$/);
1559
+ if (sitesMatch) {
1560
+ const accountId = sitesMatch[1];
1561
+ const client = await AdSenseClient.create(accountId);
1562
+ const sites = await client.listSites(accountId);
1563
+ return {
1564
+ contents: [
1565
+ {
1566
+ uri,
1567
+ mimeType: "application/json",
1568
+ text: JSON.stringify(sites, null, 2)
1569
+ }
1570
+ ]
1571
+ };
1572
+ }
1573
+ const adUnitsMatch = path2.match(/^accounts\/([^/]+)\/adunits$/);
1574
+ if (adUnitsMatch) {
1575
+ const accountId = adUnitsMatch[1];
1576
+ const client = await AdSenseClient.create(accountId);
1577
+ const adUnits = await client.listAdUnits(accountId);
1578
+ return {
1579
+ contents: [
1580
+ {
1581
+ uri,
1582
+ mimeType: "application/json",
1583
+ text: JSON.stringify(adUnits, null, 2)
1584
+ }
1585
+ ]
1586
+ };
1587
+ }
1588
+ throw new Error(`Unknown resource: ${uri}`);
1589
+ }
1590
+
1591
+ // src/server/index.ts
1592
+ function createServer() {
1593
+ const server = new Server(
1594
+ {
1595
+ name: "adsense-mcp-server",
1596
+ version: "0.1.0"
1597
+ },
1598
+ {
1599
+ capabilities: {
1600
+ tools: {},
1601
+ resources: {}
1602
+ }
1603
+ }
1604
+ );
1605
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
1606
+ return {
1607
+ tools: registerTools()
1608
+ };
1609
+ });
1610
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1611
+ return handleToolCall(request.params.name, request.params.arguments || {});
1612
+ });
1613
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
1614
+ return handleListResources();
1615
+ });
1616
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
1617
+ return handleReadResource(request.params.uri);
1618
+ });
1619
+ return server;
1620
+ }
1621
+ async function startServer(accountId) {
1622
+ if (accountId) {
1623
+ process.env.ADSENSE_ACCOUNT_ID = accountId;
1624
+ }
1625
+ const server = createServer();
1626
+ const transport = new StdioServerTransport();
1627
+ await server.connect(transport);
1628
+ process.on("SIGINT", async () => {
1629
+ await server.close();
1630
+ process.exit(0);
1631
+ });
1632
+ process.on("SIGTERM", async () => {
1633
+ await server.close();
1634
+ process.exit(0);
1635
+ });
1636
+ }
1637
+ export {
1638
+ ADSENSE_SCOPES,
1639
+ AdSenseClient,
1640
+ createOAuthClient,
1641
+ createServer,
1642
+ createServiceAccountClient,
1643
+ startServer
1644
+ };
1645
+ //# sourceMappingURL=index.js.map