@channel47/google-ads-mcp 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Channel47
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # @channel47/google-ads-mcp
2
+
3
+ [![npm version](https://badge.fury.io/js/@channel47%2Fgoogle-ads-mcp.svg)](https://www.npmjs.com/package/@channel47/google-ads-mcp)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ MCP server for Google Ads API access via GAQL (Google Ads Query Language).
7
+
8
+ ## Overview
9
+
10
+ This is a Model Context Protocol (MCP) server that provides tools for querying and mutating Google Ads data using GAQL. It's designed to work seamlessly with Claude Code and other MCP-compatible clients.
11
+
12
+ ## Installation
13
+
14
+ ### For Claude Code Plugin Users
15
+
16
+ This package is automatically installed when using the [google-ads-specialist Claude Code plugin](https://marketplace.claude.com/plugins/google-ads-specialist). No manual installation required!
17
+
18
+ ### Standalone Use
19
+
20
+ For use with other MCP clients or standalone testing:
21
+
22
+ ```bash
23
+ npx @channel47/google-ads-mcp@latest
24
+ ```
25
+
26
+ Or install globally:
27
+
28
+ ```bash
29
+ npm install -g @channel47/google-ads-mcp
30
+ google-ads-mcp
31
+ ```
32
+
33
+ ## Configuration
34
+
35
+ Set these environment variables before running:
36
+
37
+ ### Required
38
+
39
+ | Variable | Description |
40
+ |----------|-------------|
41
+ | `GOOGLE_ADS_DEVELOPER_TOKEN` | Your Google Ads API Developer Token |
42
+ | `GOOGLE_ADS_CLIENT_ID` | OAuth 2.0 Client ID from Google Cloud |
43
+ | `GOOGLE_ADS_CLIENT_SECRET` | OAuth 2.0 Client Secret |
44
+ | `GOOGLE_ADS_REFRESH_TOKEN` | OAuth 2.0 Refresh Token |
45
+
46
+ ### Optional
47
+
48
+ | Variable | Description |
49
+ |----------|-------------|
50
+ | `GOOGLE_ADS_LOGIN_CUSTOMER_ID` | MCC Account ID (10 digits, no dashes) |
51
+ | `GOOGLE_ADS_DEFAULT_CUSTOMER_ID` | Default account ID for queries |
52
+
53
+ ## Tools
54
+
55
+ ### list_accounts
56
+
57
+ List all accessible Google Ads accounts.
58
+
59
+ **Returns:** Array of account objects with ID, name, currency, and status.
60
+
61
+ ### query
62
+
63
+ Execute any GAQL SELECT query. Returns clean JSON results.
64
+
65
+ **Parameters:**
66
+ - `customer_id` (string, optional): Account ID (10 digits, no dashes)
67
+ - `query` (string, required): GAQL SELECT query
68
+ - `limit` (integer, optional): Max rows to return (default: 100, max: 10000)
69
+
70
+ **Example:**
71
+ ```javascript
72
+ {
73
+ "customer_id": "1234567890",
74
+ "query": "SELECT campaign.name, campaign.status FROM campaign WHERE campaign.status = 'ENABLED'",
75
+ "limit": 50
76
+ }
77
+ ```
78
+
79
+ ### mutate
80
+
81
+ Execute write operations using GoogleAdsService.Mutate.
82
+
83
+ **Parameters:**
84
+ - `customer_id` (string, optional): Account ID
85
+ - `operations` (array, required): Mutation operations
86
+ - `partial_failure` (boolean, optional): Enable partial failure mode (default: true)
87
+ - `dry_run` (boolean, optional): Validate without executing (default: true for safety)
88
+
89
+ **Example:**
90
+ ```javascript
91
+ {
92
+ "customer_id": "1234567890",
93
+ "operations": [
94
+ {
95
+ "campaignOperation": {
96
+ "update": {
97
+ "resourceName": "customers/1234567890/campaigns/123",
98
+ "status": "PAUSED"
99
+ },
100
+ "updateMask": "status"
101
+ }
102
+ }
103
+ ],
104
+ "dry_run": false
105
+ }
106
+ ```
107
+
108
+ ## Resources & Prompts
109
+
110
+ The server provides:
111
+ - **Resources**: GAQL reference documentation accessible via MCP resources
112
+ - **Prompts**: Templates for common Google Ads operations
113
+
114
+ ## Usage with Claude Code
115
+
116
+ This server is designed to work with the [google-ads-specialist plugin](https://marketplace.claude.com/plugins/google-ads-specialist), which provides:
117
+
118
+ - **9 Skill Files**: Progressive disclosure of GAQL patterns and best practices
119
+ - Atomic skills for focused tasks (campaign performance, search terms, wasted spend, etc.)
120
+ - Playbooks for comprehensive workflows (account health audit)
121
+ - Troubleshooting guides for common errors
122
+ - **PreToolUse Hook**: Validates skill references before query/mutate operations
123
+ - **Comprehensive Documentation**: Setup guides and OAuth configuration help
124
+
125
+ The plugin ensures Claude consults domain knowledge before executing queries, preventing hallucinated GAQL.
126
+
127
+ ## Development
128
+
129
+ ### Prerequisites
130
+
131
+ - Node.js 18 or higher
132
+ - Google Ads API access (Developer Token)
133
+ - OAuth 2.0 credentials from Google Cloud
134
+
135
+ ### Setup
136
+
137
+ ```bash
138
+ git clone https://github.com/channel47/google-ads-mcp-server.git
139
+ cd google-ads-mcp-server
140
+ npm install
141
+ ```
142
+
143
+ ### Testing
144
+
145
+ ```bash
146
+ npm test
147
+ ```
148
+
149
+ ### Running Locally
150
+
151
+ ```bash
152
+ npm start
153
+ ```
154
+
155
+ ## Architecture
156
+
157
+ **Minimal Design:**
158
+ - ~200 lines of server code
159
+ - 3 core tools (list, query, mutate)
160
+ - OAuth 2.0 authentication
161
+ - Resources for GAQL reference
162
+ - Prompts for common patterns
163
+
164
+ **Security:**
165
+ - Dry-run mode enabled by default for mutations
166
+ - Environment-based credential management
167
+ - Input validation for all operations
168
+
169
+ ## Contributing
170
+
171
+ Contributions welcome! Please:
172
+
173
+ 1. Fork the repository
174
+ 2. Create a feature branch
175
+ 3. Make your changes with tests
176
+ 4. Submit a pull request
177
+
178
+ ## Links
179
+
180
+ - [NPM Package](https://www.npmjs.com/package/@channel47/google-ads-mcp)
181
+ - [GitHub Repository](https://github.com/channel47/google-ads-mcp-server)
182
+ - [Claude Code Plugin](https://marketplace.claude.com/plugins/google-ads-specialist)
183
+ - [Google Ads API Documentation](https://developers.google.com/google-ads/api/docs/start)
184
+ - [GAQL Reference](https://developers.google.com/google-ads/api/docs/query/overview)
185
+ - [Model Context Protocol](https://modelcontextprotocol.io)
186
+
187
+ ## License
188
+
189
+ MIT - See [LICENSE](LICENSE) file for details.
190
+
191
+ ## Support
192
+
193
+ For issues or questions:
194
+ - Plugin-related: [Plugin Repository Issues](https://github.com/ctrlswing/channel47-marketplace/issues)
195
+ - Server-related: [Server Repository Issues](https://github.com/channel47/google-ads-mcp-server/issues)
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@channel47/google-ads-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Google Ads MCP Server - Query and mutate Google Ads data using GAQL",
5
+ "main": "server/index.js",
6
+ "bin": {
7
+ "google-ads-mcp": "server/index.js"
8
+ },
9
+ "type": "module",
10
+ "files": [
11
+ "server/**/*.js",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "start": "node server/index.js",
17
+ "test": "node --test *.test.js test/*.test.js",
18
+ "lint": "eslint server/",
19
+ "prepublishOnly": "npm test"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "google-ads",
25
+ "gaql",
26
+ "analytics",
27
+ "advertising",
28
+ "claude"
29
+ ],
30
+ "author": "Channel47",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/channel47/google-ads-mcp-server.git"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/channel47/google-ads-mcp-server/issues"
38
+ },
39
+ "homepage": "https://github.com/channel47/google-ads-mcp-server#readme",
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.0.0",
42
+ "google-ads-api": "^21.0.1",
43
+ "google-auth-library": "^9.0.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=18.0.0"
47
+ }
48
+ }
package/server/auth.js ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+ import { GoogleAdsApi } from 'google-ads-api';
3
+
4
+ // Required environment variables for OAuth 2.0 authentication
5
+ const REQUIRED_ENV_VARS = [
6
+ 'GOOGLE_ADS_DEVELOPER_TOKEN',
7
+ 'GOOGLE_ADS_CLIENT_ID',
8
+ 'GOOGLE_ADS_CLIENT_SECRET',
9
+ 'GOOGLE_ADS_REFRESH_TOKEN',
10
+ ];
11
+
12
+ /**
13
+ * Validate all required environment variables are present
14
+ * @returns {{ valid: boolean, missing: string[] }}
15
+ */
16
+ export function validateEnvironment() {
17
+ const missing = REQUIRED_ENV_VARS.filter(varName => !process.env[varName]);
18
+
19
+ return {
20
+ valid: missing.length === 0,
21
+ missing
22
+ };
23
+ }
24
+
25
+ let cachedClient = null;
26
+
27
+ /**
28
+ * Initialize and return Google Ads API client
29
+ * Caches client instance for reuse
30
+ * @returns {GoogleAdsApi}
31
+ * @throws {Error} If environment variables are missing or invalid
32
+ */
33
+ export function initializeGoogleAdsClient() {
34
+ if (cachedClient) {
35
+ return cachedClient;
36
+ }
37
+
38
+ // Validate environment first
39
+ const { valid, missing } = validateEnvironment();
40
+ if (!valid) {
41
+ throw new Error(
42
+ `Missing required environment variables: ${missing.join(', ')}\n` +
43
+ `Please set these variables before starting the server.`
44
+ );
45
+ }
46
+
47
+ // Initialize Google Ads client
48
+ try {
49
+ cachedClient = new GoogleAdsApi({
50
+ client_id: process.env.GOOGLE_ADS_CLIENT_ID,
51
+ client_secret: process.env.GOOGLE_ADS_CLIENT_SECRET,
52
+ developer_token: process.env.GOOGLE_ADS_DEVELOPER_TOKEN,
53
+ });
54
+
55
+ return cachedClient;
56
+ } catch (error) {
57
+ throw new Error(`Failed to initialize Google Ads client: ${error.message}`);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Get authenticated customer client for a specific account
63
+ * @param {string} customerId - Google Ads account ID (without dashes)
64
+ * @returns {Customer}
65
+ */
66
+ export function getCustomerClient(customerId) {
67
+ const client = initializeGoogleAdsClient();
68
+
69
+ return client.Customer({
70
+ customer_id: customerId,
71
+ refresh_token: process.env.GOOGLE_ADS_REFRESH_TOKEN,
72
+ login_customer_id: process.env.GOOGLE_ADS_LOGIN_CUSTOMER_ID,
73
+ });
74
+ }
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
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
+ ListPromptsRequestSchema,
8
+ GetPromptRequestSchema,
9
+ ListResourcesRequestSchema,
10
+ ReadResourceRequestSchema,
11
+ } from '@modelcontextprotocol/sdk/types.js';
12
+
13
+ // Import auth for validation
14
+ import { validateEnvironment } from './auth.js';
15
+
16
+ // Import tools
17
+ import { listAccounts } from './tools/list-accounts.js';
18
+ import { runGaqlQuery } from './tools/gaql-query.js';
19
+ import { mutate } from './tools/mutate.js';
20
+
21
+ // Import resources
22
+ import { getResourcesList, readResource } from './resources/index.js';
23
+
24
+ // Import prompts
25
+ import { getPromptsList, renderPrompt } from './prompts/templates.js';
26
+
27
+ // Server metadata
28
+ const SERVER_NAME = 'google-ads-mcp';
29
+ const SERVER_VERSION = '1.0.0';
30
+
31
+ // Validate environment on startup
32
+ const { valid, missing } = validateEnvironment();
33
+ if (!valid) {
34
+ console.error(`Missing required environment variables: ${missing.join(', ')}`);
35
+ process.exit(1);
36
+ }
37
+
38
+ // Create MCP server
39
+ const server = new Server(
40
+ {
41
+ name: SERVER_NAME,
42
+ version: SERVER_VERSION,
43
+ },
44
+ {
45
+ capabilities: {
46
+ tools: {},
47
+ prompts: {},
48
+ resources: {},
49
+ },
50
+ }
51
+ );
52
+
53
+ // Tool definitions
54
+ const TOOLS = [
55
+ {
56
+ name: 'list_accounts',
57
+ description: 'List all accessible Google Ads accounts under the authenticated user or MCC. Use this first to find account IDs before running other tools.',
58
+ inputSchema: {
59
+ type: 'object',
60
+ properties: {
61
+ include_manager_accounts: {
62
+ type: 'boolean',
63
+ description: 'Include manager (MCC) accounts in results',
64
+ default: false
65
+ }
66
+ }
67
+ }
68
+ },
69
+ {
70
+ name: 'query',
71
+ description: 'Execute any GAQL SELECT query. Returns clean JSON results. Mutations are blocked - use mutate tool for write operations.',
72
+ inputSchema: {
73
+ type: 'object',
74
+ properties: {
75
+ customer_id: {
76
+ type: 'string',
77
+ description: 'Google Ads account ID (optional, uses default if not specified)'
78
+ },
79
+ query: {
80
+ type: 'string',
81
+ description: 'Full GAQL query string (SELECT only)'
82
+ },
83
+ limit: {
84
+ type: 'integer',
85
+ description: 'Maximum rows to return (default: 100, max: 10000)',
86
+ default: 100
87
+ }
88
+ },
89
+ required: ['query']
90
+ }
91
+ },
92
+ {
93
+ name: 'mutate',
94
+ description: 'Execute write operations using GoogleAdsService.Mutate. Supports all operation types. Default dry_run=true for safety.',
95
+ inputSchema: {
96
+ type: 'object',
97
+ properties: {
98
+ customer_id: {
99
+ type: 'string',
100
+ description: 'Google Ads customer ID (optional, uses default if not specified)'
101
+ },
102
+ operations: {
103
+ type: 'array',
104
+ description: 'Array of mutation operation objects',
105
+ items: {
106
+ type: 'object'
107
+ }
108
+ },
109
+ partial_failure: {
110
+ type: 'boolean',
111
+ description: 'Enable partial failure mode (default: true)',
112
+ default: true
113
+ },
114
+ dry_run: {
115
+ type: 'boolean',
116
+ description: 'Validate only, do not execute (default: true)',
117
+ default: true
118
+ }
119
+ },
120
+ required: ['operations']
121
+ }
122
+ }
123
+ ];
124
+
125
+ // Register list_tools handler
126
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
127
+ return { tools: TOOLS };
128
+ });
129
+
130
+ // Register call_tool handler
131
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
132
+ const { name, arguments: params } = request.params;
133
+
134
+ try {
135
+ switch (name) {
136
+ case 'list_accounts':
137
+ return await listAccounts(params);
138
+
139
+ case 'query':
140
+ return await runGaqlQuery(params);
141
+
142
+ case 'mutate':
143
+ return await mutate(params);
144
+
145
+ default:
146
+ throw new Error(`Unknown tool: ${name}`);
147
+ }
148
+ } catch (error) {
149
+ console.error(`Tool error (${name}):`, error);
150
+ throw error;
151
+ }
152
+ });
153
+
154
+ // Register list_prompts handler
155
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
156
+ return { prompts: getPromptsList() };
157
+ });
158
+
159
+ // Register get_prompt handler
160
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
161
+ const { name, arguments: args } = request.params;
162
+
163
+ try {
164
+ return renderPrompt(name, args || {});
165
+ } catch (error) {
166
+ console.error(`Prompt error (${name}):`, error);
167
+ throw error;
168
+ }
169
+ });
170
+
171
+ // Register list_resources handler
172
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
173
+ return { resources: getResourcesList() };
174
+ });
175
+
176
+ // Register read_resource handler
177
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
178
+ const { uri } = request.params;
179
+
180
+ try {
181
+ return readResource(uri);
182
+ } catch (error) {
183
+ console.error(`Resource error (${uri}):`, error);
184
+ throw error;
185
+ }
186
+ });
187
+
188
+ // Start server
189
+ async function main() {
190
+ const transport = new StdioServerTransport();
191
+ await server.connect(transport);
192
+
193
+ console.error(`${SERVER_NAME} v${SERVER_VERSION} started`);
194
+ }
195
+
196
+ main().catch((error) => {
197
+ console.error('Fatal error:', error);
198
+ process.exit(1);
199
+ });