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