admob-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,259 @@
1
+ import { z } from 'zod';
2
+ import { AD_FORMATS, PLATFORMS } from '../admob/constants.js';
3
+ import { paginationShape, runTool } from './common.js';
4
+ const mediationGroupLineSchema = z.object({
5
+ id: z
6
+ .string()
7
+ .optional()
8
+ .describe('Line ID. For new lines use distinct negative placeholders ("-1", "-2", ...)'),
9
+ displayName: z.string().describe('Display name of the line'),
10
+ adSourceId: z.string().describe('Ad source this line serves — get IDs from list_ad_sources'),
11
+ cpmMode: z
12
+ .enum(['LIVE', 'MANUAL', 'ANO'])
13
+ .optional()
14
+ .describe('CPM mode: LIVE (real-time from the network), MANUAL (fixed CPM), ANO (network-optimized)'),
15
+ cpmMicros: z
16
+ .number()
17
+ .optional()
18
+ .describe('Fixed CPM in micros of the account currency (only with cpmMode MANUAL)'),
19
+ adUnitMappings: z
20
+ .record(z.string(), z.string())
21
+ .optional()
22
+ .describe('Map of AdMob ad unit ID to ad unit mapping resource name (from create_ad_unit_mapping)'),
23
+ });
24
+ function toApiLine(line) {
25
+ return {
26
+ id: line.id,
27
+ displayName: line.displayName,
28
+ adSourceId: line.adSourceId,
29
+ cpmMode: line.cpmMode,
30
+ cpmMicros: line.cpmMicros != null ? String(line.cpmMicros) : undefined,
31
+ adUnitMappings: line.adUnitMappings,
32
+ };
33
+ }
34
+ export function registerMediationToolset(ctx) {
35
+ ctx.server.registerTool('list_ad_sources', {
36
+ title: 'List ad sources',
37
+ description: 'Lists the mediation ad sources (ad networks) available to the AdMob account, with their ad source IDs.',
38
+ inputSchema: { ...paginationShape },
39
+ annotations: { readOnlyHint: true },
40
+ }, async (args) => runTool(async () => {
41
+ const client = await ctx.client();
42
+ const res = await client.accounts.adSources.list({
43
+ parent: await ctx.accountName(),
44
+ pageSize: args.pageSize,
45
+ pageToken: args.pageToken,
46
+ });
47
+ return res.data;
48
+ }));
49
+ ctx.server.registerTool('list_adapters', {
50
+ title: 'List ad source adapters',
51
+ description: 'Lists the adapters of a mediation ad source, including the adapter ID and required ad unit configuration keys (needed for create_ad_unit_mapping).',
52
+ inputSchema: {
53
+ adSourceId: z.string().describe('Ad source ID from list_ad_sources'),
54
+ ...paginationShape,
55
+ },
56
+ annotations: { readOnlyHint: true },
57
+ }, async (args) => runTool(async () => {
58
+ const client = await ctx.client();
59
+ const res = await client.accounts.adSources.adapters.list({
60
+ parent: `${await ctx.accountName()}/adSources/${args.adSourceId}`,
61
+ pageSize: args.pageSize,
62
+ pageToken: args.pageToken,
63
+ });
64
+ return res.data;
65
+ }));
66
+ ctx.server.registerTool('list_mediation_groups', {
67
+ title: 'List mediation groups',
68
+ description: 'Lists the mediation groups in the AdMob account, including targeting (format, platform, ad units) and mediation lines.',
69
+ inputSchema: {
70
+ ...paginationShape,
71
+ filter: z
72
+ .string()
73
+ .optional()
74
+ .describe('Optional filter, e.g. \'AD_SOURCE_IDS IN ("5450213213286189855")\' or \'DISPLAY_NAME CONTAINS "waterfall"\''),
75
+ },
76
+ annotations: { readOnlyHint: true },
77
+ }, async (args) => runTool(async () => {
78
+ const client = await ctx.client();
79
+ const res = await client.accounts.mediationGroups.list({
80
+ parent: await ctx.accountName(),
81
+ pageSize: args.pageSize,
82
+ pageToken: args.pageToken,
83
+ filter: args.filter,
84
+ });
85
+ return res.data;
86
+ }));
87
+ ctx.server.registerTool('list_ad_unit_mappings', {
88
+ title: 'List ad unit mappings',
89
+ description: 'Lists the ad unit mappings (third-party network placements) attached to an AdMob ad unit.',
90
+ inputSchema: {
91
+ adUnitId: z
92
+ .string()
93
+ .describe('Ad unit ID, e.g. the numeric part after "/" in ca-app-pub-XXX/YYYYYYYYYY'),
94
+ ...paginationShape,
95
+ },
96
+ annotations: { readOnlyHint: true },
97
+ }, async (args) => runTool(async () => {
98
+ const client = await ctx.client();
99
+ const res = await client.accounts.adUnits.adUnitMappings.list({
100
+ parent: `${await ctx.accountName()}/adUnits/${args.adUnitId}`,
101
+ pageSize: args.pageSize,
102
+ pageToken: args.pageToken,
103
+ });
104
+ return res.data;
105
+ }));
106
+ if (ctx.readOnly)
107
+ return;
108
+ ctx.server.registerTool('create_ad_unit_mapping', {
109
+ title: 'Create ad unit mapping',
110
+ description: 'Creates an ad unit mapping that links an AdMob ad unit to a third-party network placement via an adapter. Get the adapter ID and required configuration keys from list_adapters. Requires the admob.monetization scope.',
111
+ inputSchema: {
112
+ adUnitId: z.string().describe('AdMob ad unit ID to attach the mapping to'),
113
+ adapterId: z.string().describe('Adapter ID from list_adapters'),
114
+ adUnitConfigurations: z
115
+ .record(z.string(), z.string())
116
+ .describe("Adapter configuration key/value pairs, keys from the adapter's adUnitConfigurations"),
117
+ displayName: z.string().optional().describe('Display name of the mapping'),
118
+ },
119
+ annotations: { readOnlyHint: false },
120
+ }, async (args) => runTool(async () => {
121
+ const client = await ctx.client();
122
+ const res = await client.accounts.adUnits.adUnitMappings.create({
123
+ parent: `${await ctx.accountName()}/adUnits/${args.adUnitId}`,
124
+ requestBody: {
125
+ adapterId: args.adapterId,
126
+ adUnitConfigurations: args.adUnitConfigurations,
127
+ displayName: args.displayName,
128
+ },
129
+ });
130
+ return res.data;
131
+ }));
132
+ ctx.server.registerTool('create_mediation_group', {
133
+ title: 'Create mediation group',
134
+ description: 'Creates a mediation group targeting a format/platform and a set of ad units, with optional mediation lines for third-party ad sources. Requires the admob.monetization scope.',
135
+ inputSchema: {
136
+ displayName: z.string().describe('Display name of the mediation group'),
137
+ platform: z.enum(PLATFORMS).describe('Targeted platform'),
138
+ format: z.enum(AD_FORMATS).describe('Targeted ad format'),
139
+ adUnitIds: z
140
+ .array(z.string())
141
+ .min(1)
142
+ .describe('AdMob ad unit IDs this mediation group serves'),
143
+ targetedRegionCodes: z
144
+ .array(z.string())
145
+ .optional()
146
+ .describe('CLDR region codes to target (omit for all regions)'),
147
+ excludedRegionCodes: z
148
+ .array(z.string())
149
+ .optional()
150
+ .describe('CLDR region codes to exclude'),
151
+ idfaTargeting: z
152
+ .enum(['ALL', 'AVAILABLE', 'UNAVAILABLE'])
153
+ .optional()
154
+ .describe('iOS IDFA targeting'),
155
+ mediationGroupLines: z
156
+ .array(mediationGroupLineSchema)
157
+ .optional()
158
+ .describe('Mediation lines (ad sources) in the group'),
159
+ },
160
+ annotations: { readOnlyHint: false },
161
+ }, async (args) => runTool(async () => {
162
+ const client = await ctx.client();
163
+ const lines = args.mediationGroupLines?.map((line, i) => toApiLine({ ...line, id: line.id ?? String(-(i + 1)) }));
164
+ const res = await client.accounts.mediationGroups.create({
165
+ parent: await ctx.accountName(),
166
+ requestBody: {
167
+ displayName: args.displayName,
168
+ targeting: {
169
+ platform: args.platform,
170
+ format: args.format,
171
+ adUnitIds: args.adUnitIds,
172
+ targetedRegionCodes: args.targetedRegionCodes,
173
+ excludedRegionCodes: args.excludedRegionCodes,
174
+ idfaTargeting: args.idfaTargeting,
175
+ },
176
+ mediationGroupLines: lines
177
+ ? Object.fromEntries(lines.map((line) => [line.id ?? '', line]))
178
+ : undefined,
179
+ },
180
+ });
181
+ return res.data;
182
+ }));
183
+ ctx.server.registerTool('update_mediation_group', {
184
+ title: 'Update mediation group',
185
+ description: 'Patches a mediation group. Pass the fields to change in mediationGroup and list their paths in updateMask (e.g. "display_name,targeting.ad_unit_ids"). Requires the admob.monetization scope.',
186
+ inputSchema: {
187
+ mediationGroupId: z.string().describe('Mediation group ID from list_mediation_groups'),
188
+ updateMask: z
189
+ .string()
190
+ .describe('Comma-separated field mask, e.g. "display_name,mediation_group_lines"'),
191
+ mediationGroup: z
192
+ .record(z.string(), z.unknown())
193
+ .describe('MediationGroup fields to update, following the AdMob API v1beta MediationGroup schema'),
194
+ },
195
+ annotations: { readOnlyHint: false },
196
+ }, async (args) => runTool(async () => {
197
+ const client = await ctx.client();
198
+ const res = await client.accounts.mediationGroups.patch({
199
+ name: `${await ctx.accountName()}/mediationGroups/${args.mediationGroupId}`,
200
+ updateMask: args.updateMask,
201
+ // Passthrough by design: the field mask decides the shape; Google validates it.
202
+ requestBody: args.mediationGroup,
203
+ });
204
+ return res.data;
205
+ }));
206
+ ctx.server.registerTool('create_mediation_ab_experiment', {
207
+ title: 'Create mediation A/B experiment',
208
+ description: 'Creates an A/B experiment on a mediation group: the treatment variant serves the given mediation lines against the existing setup. Requires the admob.monetization scope.',
209
+ inputSchema: {
210
+ mediationGroupId: z.string().describe('Mediation group ID to experiment on'),
211
+ displayName: z.string().describe('Display name of the experiment'),
212
+ treatmentTrafficPercentage: z
213
+ .number()
214
+ .int()
215
+ .min(1)
216
+ .max(99)
217
+ .optional()
218
+ .describe('Percentage of traffic sent to the treatment variant (1-99)'),
219
+ treatmentMediationLines: z
220
+ .array(mediationGroupLineSchema)
221
+ .min(1)
222
+ .describe('Mediation lines served by the treatment variant'),
223
+ },
224
+ annotations: { readOnlyHint: false },
225
+ }, async (args) => runTool(async () => {
226
+ const client = await ctx.client();
227
+ const res = await client.accounts.mediationGroups.mediationAbExperiments.create({
228
+ parent: `${await ctx.accountName()}/mediationGroups/${args.mediationGroupId}`,
229
+ requestBody: {
230
+ displayName: args.displayName,
231
+ treatmentTrafficPercentage: args.treatmentTrafficPercentage != null
232
+ ? String(args.treatmentTrafficPercentage)
233
+ : undefined,
234
+ treatmentMediationLines: args.treatmentMediationLines.map((line) => ({
235
+ mediationGroupLine: toApiLine(line),
236
+ })),
237
+ },
238
+ });
239
+ return res.data;
240
+ }));
241
+ ctx.server.registerTool('stop_mediation_ab_experiment', {
242
+ title: 'Stop mediation A/B experiment',
243
+ description: 'Stops the running A/B experiment on a mediation group by choosing the winning variant. Requires the admob.monetization scope.',
244
+ inputSchema: {
245
+ mediationGroupId: z.string().describe('Mediation group ID whose experiment to stop'),
246
+ variantChoice: z
247
+ .enum(['VARIANT_CHOICE_A', 'VARIANT_CHOICE_B'])
248
+ .describe('Winning variant to keep serving (A = control, B = treatment)'),
249
+ },
250
+ annotations: { readOnlyHint: false },
251
+ }, async (args) => runTool(async () => {
252
+ const client = await ctx.client();
253
+ const res = await client.accounts.mediationGroups.mediationAbExperiments.stop({
254
+ name: `${await ctx.accountName()}/mediationGroups/${args.mediationGroupId}/mediationAbExperiments`,
255
+ requestBody: { variantChoice: args.variantChoice },
256
+ });
257
+ return res.data;
258
+ }));
259
+ }
@@ -0,0 +1,108 @@
1
+ import { z } from 'zod';
2
+ import { CAMPAIGN_REPORT_DIMENSIONS, CAMPAIGN_REPORT_METRICS, DEFAULT_MAX_REPORT_ROWS, MAX_REPORT_ROWS_LIMIT, MEDIATION_REPORT_DIMENSIONS, MEDIATION_REPORT_METRICS, NETWORK_REPORT_DIMENSIONS, NETWORK_REPORT_METRICS, SORT_ORDERS, } from '../admob/constants.js';
3
+ import { buildReportSpec, flattenReport, flattenRow, parseDate } from '../admob/reports.js';
4
+ import { runTool } from './common.js';
5
+ const dateSchema = z
6
+ .string()
7
+ .regex(/^\d{4}-\d{2}-\d{2}$/, 'Use YYYY-MM-DD')
8
+ .describe('Date in YYYY-MM-DD format');
9
+ function reportInputShape(dimensions, metrics) {
10
+ return {
11
+ startDate: dateSchema.describe('Start date (YYYY-MM-DD), inclusive'),
12
+ endDate: dateSchema.describe('End date (YYYY-MM-DD), inclusive'),
13
+ metrics: z.array(z.enum(metrics)).min(1).describe('Metrics to report'),
14
+ dimensions: z.array(z.enum(dimensions)).optional().describe('Dimensions to group rows by'),
15
+ dimensionFilters: z
16
+ .array(z.object({
17
+ dimension: z.enum(dimensions),
18
+ values: z.array(z.string()).min(1).describe('Dimension values to match'),
19
+ }))
20
+ .optional()
21
+ .describe('Only include rows whose dimension matches one of the given values'),
22
+ sortConditions: z
23
+ .array(z.object({
24
+ dimension: z.enum(dimensions).optional(),
25
+ metric: z.enum(metrics).optional(),
26
+ order: z.enum(SORT_ORDERS),
27
+ }))
28
+ .optional()
29
+ .describe('Sort order — set either dimension or metric per condition'),
30
+ maxReportRows: z
31
+ .number()
32
+ .int()
33
+ .min(1)
34
+ .max(MAX_REPORT_ROWS_LIMIT)
35
+ .optional()
36
+ .describe(`Maximum rows to return (default ${DEFAULT_MAX_REPORT_ROWS})`),
37
+ currencyCode: z
38
+ .string()
39
+ .length(3)
40
+ .optional()
41
+ .describe('ISO 4217 currency for monetary metrics (default: account currency)'),
42
+ };
43
+ }
44
+ const MONETARY_NOTE = 'Monetary metrics are converted from micros to whole currency units (see currencyCode).';
45
+ export function registerReportsToolset(ctx) {
46
+ ctx.server.registerTool('generate_network_report', {
47
+ title: 'Generate AdMob network report',
48
+ description: `Generates an AdMob Network earnings/performance report (requests, impressions, clicks, estimated earnings, match rate, RPM...). ${MONETARY_NOTE}`,
49
+ inputSchema: reportInputShape(NETWORK_REPORT_DIMENSIONS, NETWORK_REPORT_METRICS),
50
+ annotations: { readOnlyHint: true },
51
+ }, async (args) => runTool(async () => {
52
+ const client = await ctx.client();
53
+ const res = await client.accounts.networkReport.generate({
54
+ parent: await ctx.accountName(),
55
+ requestBody: { reportSpec: buildReportSpec(args) },
56
+ });
57
+ return flattenReport(res.data);
58
+ }));
59
+ ctx.server.registerTool('generate_mediation_report', {
60
+ title: 'Generate AdMob mediation report',
61
+ description: `Generates an AdMob Mediation report across ad sources (waterfall/bidding): impressions, estimated earnings, observed eCPM per AD_SOURCE, MEDIATION_GROUP, and more. ${MONETARY_NOTE}`,
62
+ inputSchema: reportInputShape(MEDIATION_REPORT_DIMENSIONS, MEDIATION_REPORT_METRICS),
63
+ annotations: { readOnlyHint: true },
64
+ }, async (args) => runTool(async () => {
65
+ const client = await ctx.client();
66
+ const res = await client.accounts.mediationReport.generate({
67
+ parent: await ctx.accountName(),
68
+ requestBody: { reportSpec: buildReportSpec(args) },
69
+ });
70
+ return flattenReport(res.data);
71
+ }));
72
+ ctx.server.registerTool('generate_campaign_report', {
73
+ title: 'Generate AdMob campaign report',
74
+ description: `Generates a report for AdMob cross-promotion campaigns: impressions, clicks, installs, estimated cost. Date range must be within the last 30 days. ${MONETARY_NOTE}`,
75
+ inputSchema: {
76
+ startDate: dateSchema.describe('Start date (YYYY-MM-DD), inclusive, within the last 30 days'),
77
+ endDate: dateSchema.describe('End date (YYYY-MM-DD), inclusive'),
78
+ metrics: z.array(z.enum(CAMPAIGN_REPORT_METRICS)).min(1).describe('Metrics to report'),
79
+ dimensions: z
80
+ .array(z.enum(CAMPAIGN_REPORT_DIMENSIONS))
81
+ .optional()
82
+ .describe('Dimensions to group rows by'),
83
+ languageCode: z
84
+ .string()
85
+ .optional()
86
+ .describe('IETF language tag for localized text, e.g. "en-US"'),
87
+ },
88
+ annotations: { readOnlyHint: true },
89
+ }, async (args) => runTool(async () => {
90
+ const client = await ctx.client();
91
+ const res = await client.accounts.campaignReport.generate({
92
+ parent: await ctx.accountName(),
93
+ requestBody: {
94
+ reportSpec: {
95
+ dateRange: {
96
+ startDate: parseDate(args.startDate),
97
+ endDate: parseDate(args.endDate),
98
+ },
99
+ metrics: args.metrics,
100
+ dimensions: args.dimensions,
101
+ languageCode: args.languageCode,
102
+ },
103
+ },
104
+ });
105
+ const rows = (res.data.rows ?? []).map(flattenRow);
106
+ return { rowCount: rows.length, rows };
107
+ }));
108
+ }
@@ -0,0 +1,4 @@
1
+ import { createRequire } from 'node:module';
2
+ const require = createRequire(import.meta.url);
3
+ const pkg = require('../package.json');
4
+ export const VERSION = pkg.version;
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "admob-mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for the Google AdMob API — apps, ad units, mediation, and revenue reports",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Ted Park <gun0912@naver.com> (https://github.com/ParkSangGwon)",
8
+ "mcpName": "io.github.parksanggwon/admob-mcp-server",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/ParkSangGwon/admob-mcp-server.git"
12
+ },
13
+ "homepage": "https://github.com/ParkSangGwon/admob-mcp-server#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/ParkSangGwon/admob-mcp-server/issues"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "modelcontextprotocol",
20
+ "mcp-server",
21
+ "admob",
22
+ "google-admob",
23
+ "admob-api",
24
+ "monetization",
25
+ "ad-revenue",
26
+ "claude",
27
+ "llm"
28
+ ],
29
+ "bin": {
30
+ "admob-mcp-server": "dist/index.js"
31
+ },
32
+ "main": "dist/index.js",
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "engines": {
37
+ "node": ">=18"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc && shx chmod +x dist/index.js",
41
+ "watch": "tsc --watch",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest",
44
+ "typecheck": "tsc --noEmit",
45
+ "lint": "eslint src tests",
46
+ "format": "prettier --write .",
47
+ "format:check": "prettier --check .",
48
+ "inspect": "npm run build && npx @modelcontextprotocol/inspector node dist/index.js",
49
+ "prepare": "npm run build"
50
+ },
51
+ "dependencies": {
52
+ "@googleapis/admob": "^6.0.2",
53
+ "@modelcontextprotocol/sdk": "^1.29.0",
54
+ "googleapis-common": "^8.0.2",
55
+ "zod": "^4.4.3"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^22.0.0",
59
+ "eslint": "^9.0.0",
60
+ "prettier": "^3.9.5",
61
+ "shx": "^0.4.0",
62
+ "typescript": "^5.9.3",
63
+ "typescript-eslint": "^8.0.0",
64
+ "vitest": "^4.1.10"
65
+ }
66
+ }