├── README.md ├── assets └── images │ ├── osx-1.png │ ├── osx-2.png │ ├── osx-3.png │ ├── osx-4.png │ ├── osx-5.png │ └── osx-6.png ├── build ├── client │ └── gaql-client.js └── index.js ├── package.json ├── src ├── client │ └── gaql-client.ts └── index.ts └── tsconfig.json /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Google Ads MCP (Node.js) - by TrueClicks 3 | 4 | Google Ads MCP by TrueClicks enables your AI (like Claude) to securely access and query your Google Ads account data. It supports both Windows and macOS, including Intel and ARM-based systems. It connects AIs to Google Ads using [GAQL.app] as a backend. 5 | 6 | This is an *unofficial* Google Ads MCP integration - and as of now, **no official version exists**. 7 | 8 | --- 9 | 10 | ## ✅ Why Use This MCP? 11 | 12 | Unlike other open-source Multi-Client Processors (MCPs) for Google Ads, this Node.js-based MCP offers **the easiest setup experience available**: 13 | 14 | - 🟢 **No Google Cloud Project setup required** 15 | - 🟢 No OAuth credentials 16 | - 🟢 No Developer Tokens 17 | - 🟢 No Client IDs 18 | - 🟢 No authentication hassles 19 | 20 | Instead, it uses free **[GAQL.app](https://gaql.app)**, which securely handles authentication and query execution behind the scenes. 21 | 22 | This makes it ideal for users who want to get up and running **within minutes**. 23 | 24 | --- 25 | 26 | ## 🛠️ Setup Guide 27 | 28 | ### 1. Install Node.js 29 | 30 | Ensure you have Node.js installed on your system. 31 | 32 | - On Windows, open **Command Prompt** (search for "cmd" in the Start menu) and run: 33 | 34 | ```sh 35 | winget install nodejs 36 | ``` 37 | 38 | - On macOS, download and install Node.js from [https://nodejs.org](https://nodejs.org). If you need help, please refer to the screenshots at the end of this document. 39 | 40 | ### 2. Get Your GPT Token 41 | 42 | 1. Go to [https://gaql.app](https://gaql.app) 43 | 2. Log in using your Google account to authorize Google Ads access. 44 | 3. Click the **Copy GPT Token** button in the top-right corner. 45 | 46 | ### 3. Configure Your AI (Claude) 47 | 48 | The application is configured via a JSON file named `claude_desktop_config.json`. 49 | 50 | 1. Open the **Claude** desktop application. 51 | 52 | 2. Press: 53 | 54 | - `CTRL + ,` (Control key and comma) on Windows/Linux 55 | - `Command + ,` (Command key and comma) on macOS 56 | 57 | 3. In the left sidebar, click **Developer**. 58 | 59 | 4. Open  **Edit config** and open `claude_desktop_config.json` for editing. 60 | 61 | 5. Paste the following JSON into your configuration file: 62 | 63 | ```json 64 | { 65 | "mcpServers": { 66 | "gads": { 67 | "command": "npx", 68 | "args": [ 69 | "-y", 70 | "@trueclicks/google-ads-mcp-js", 71 | "--token=YOUR_GPT_TOKEN_HERE" 72 | ] 73 | } 74 | } 75 | } 76 | ``` 77 | 78 | > **Important:** Replace `YOUR_GPT_TOKEN_HERE` with the token copied from GAQL.app. 79 | 80 | 6. Exit Claude completely: 81 | 82 | - On Windows/Linux: **Hamburger menu > File > Exit** 83 | - On macOS: Right-click the Claude icon in the top-right panel and click **Quit** 84 | 85 | 7. Restart Claude. 86 | 87 | You’re now ready to use Claude AI to query your Google Ads accounts. 88 | 89 | --- 90 | 91 | ## 💬 Example Prompts 92 | 93 | - `List my Google Ads accounts` 94 | - `What is the cost for account XYZ in the past 30 days?` 95 | - `What are the top 5 setting recommendations for my campaigns?` 96 | 97 | --- 98 | 99 | ## 🆚 Comparison with Other Google Ads MCPs 100 | 101 | | Feature | Google Ads MCP (Node.js) | Other MCPs (Python/etc.) | 102 | | -------------------------- | ------------------------ | ------------------------- | 103 | | Google Cloud project setup | 🟢 No | 🔧 Yes | 104 | | OAuth Client ID required | 🟢 No | 🔧 Yes | 105 | | Developer Token needed | 🟢 No | 🔧 Yes | 106 | | Google Ads API familiarity | 🟢 No | ⚠️ Yes | 107 | | Setup complexity | 🎉 Very low | 🟠 Moderate to high | 108 | | Backend service | ☁️ Hosted via GAQL.app | 🔧 Direct API integration | 109 | 110 | --- 111 | 112 | ## 📸 Setup Screenshots (macOS) 113 | 114 | 1. **Node.js download:** Screenshot of downloading Node.js from the official site. 115 | ![Node.js download](https://github.com/TrueClicks/google-ads-mcp-js/blob/main/assets/images/osx-1.png) 116 | 117 | 2. **Claude Developer section:** Screenshot of accessing the Developer section in Claude. 118 | ![Claude developer section](https://github.com/TrueClicks/google-ads-mcp-js/blob/main/assets/images/osx-2.png) 119 | 120 | 3. **Open configuration file for editing:** Screenshot showing how to open `claude_desktop_config.json`. 121 | ![Claude config file](https://github.com/TrueClicks/google-ads-mcp-js/blob/main/assets/images/osx-3.png) 122 | 123 | 4. **Editing configuration JSON:** Screenshot of the JSON configuration being edited. 124 | ![Content in claude_desktop_config.json](https://github.com/TrueClicks/google-ads-mcp-js/blob/main/assets/images/osx-4.png) 125 | 126 | 5. **Restarting Claude:** Screenshot showing how to quit and restart Claude from the top-right corner. 127 | ![How to hard restart Claude](https://github.com/TrueClicks/google-ads-mcp-js/blob/main/assets/images/osx-5.png) 128 | 129 | 6. **Example prompt:** Screenshot showing an example prompt using Claude with Google Ads integration. 130 | ![Example prompting Google Ads with Claude](https://github.com/TrueClicks/google-ads-mcp-js/blob/main/assets/images/osx-6.png) 131 | 132 | --- 133 | 134 | For issues or questions, please contact [ales@trueclicks.com](mailto:ales@trueclicks.com). 135 | 136 | --- 137 | -------------------------------------------------------------------------------- /assets/images/osx-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrueClicks/google-ads-mcp-js/43224622e26342ec86ff4b92b71540f06a222a2d/assets/images/osx-1.png -------------------------------------------------------------------------------- /assets/images/osx-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrueClicks/google-ads-mcp-js/43224622e26342ec86ff4b92b71540f06a222a2d/assets/images/osx-2.png -------------------------------------------------------------------------------- /assets/images/osx-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrueClicks/google-ads-mcp-js/43224622e26342ec86ff4b92b71540f06a222a2d/assets/images/osx-3.png -------------------------------------------------------------------------------- /assets/images/osx-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrueClicks/google-ads-mcp-js/43224622e26342ec86ff4b92b71540f06a222a2d/assets/images/osx-4.png -------------------------------------------------------------------------------- /assets/images/osx-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrueClicks/google-ads-mcp-js/43224622e26342ec86ff4b92b71540f06a222a2d/assets/images/osx-5.png -------------------------------------------------------------------------------- /assets/images/osx-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrueClicks/google-ads-mcp-js/43224622e26342ec86ff4b92b71540f06a222a2d/assets/images/osx-6.png -------------------------------------------------------------------------------- /build/client/gaql-client.js: -------------------------------------------------------------------------------- 1 | // src/client/gaql-client.ts 2 | const API_BASE = "https://api.gaql.app"; 3 | const USER_AGENT = "qaql-tool-client/1.0"; 4 | export class GaqlClient { 5 | token; 6 | constructor(token) { 7 | if (!token) { 8 | throw new Error("GAQL token is required"); 9 | } 10 | this.token = token; 11 | } 12 | async getAccounts() { 13 | const url = `${API_BASE}/api/gpt/google-ads/get-accounts?gptToken=${this.token}`; 14 | const response = await fetch(url, { 15 | headers: { 16 | "User-Agent": USER_AGENT 17 | } 18 | }); 19 | if (!response.ok) { 20 | const errorText = await response.text(); 21 | throw new Error(`GAQL error ${response.status}: ${errorText}`); 22 | } 23 | return response.json(); 24 | } 25 | async executeGaqlQuery(query) { 26 | const url = `${API_BASE}/api/gpt/google-ads/execute-query?gptToken=${this.token}`; 27 | const response = await fetch(url, { 28 | method: "POST", 29 | headers: { 30 | "Content-Type": "application/json", 31 | "User-Agent": USER_AGENT 32 | }, 33 | body: JSON.stringify(query) 34 | }); 35 | if (!response.ok) { 36 | const errorText = await response.text(); 37 | throw new Error(`GAQL error ${response.status}: ${errorText}`); 38 | } 39 | const json = await response.json(); 40 | return JSON.stringify(json, null, 2); // pretty print 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /build/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 4 | import { z } from "zod"; 5 | import { GaqlClient } from "./client/gaql-client.js"; 6 | // CLI token parsing 7 | console.error("Process argv:", process.argv); 8 | const tokenArg = process.argv.find(arg => arg.startsWith("--token=")); 9 | const token = tokenArg?.substring("--token=".length); 10 | if (!token) { 11 | console.error("❌ Error: Missing --token argument."); 12 | process.exit(1); 13 | } 14 | else { 15 | console.error("✅ Parsed token."); 16 | } 17 | const gaqlClient = new GaqlClient(token); 18 | const server = new McpServer({ 19 | name: "Google Ads MCP", 20 | version: "1.0.0" 21 | }); 22 | // Tool: get-accounts 23 | server.tool("get-accounts", "Gets Google Ads accounts.", async () => { 24 | console.error("🛠 get-accounts called"); 25 | try { 26 | const accounts = await gaqlClient.getAccounts(); 27 | return { 28 | content: [{ 29 | type: "text", 30 | text: `Accounts:\n${JSON.stringify(accounts, null, 2)}` 31 | }] 32 | }; 33 | } 34 | catch (err) { 35 | console.error("❌ Error in get-accounts:", err); 36 | return { 37 | content: [{ 38 | type: "text", 39 | text: `Failed to fetch accounts: ${err.message}` 40 | }] 41 | }; 42 | } 43 | }); 44 | // Tool: execute-gaql-query 45 | server.tool("execute-gaql-query", "Executes a GAQL query and returns the result as a formatted JSON string.", { 46 | query: z.string().describe("GAQL query"), 47 | customerId: z.number().describe("Customer ID"), 48 | loginCustomerId: z.number().describe("Login Customer ID"), 49 | reportAggregation: z.string().describe("Report aggregation (for Microsoft Advertising only)") 50 | }, async (args) => { 51 | console.error("🛠 execute-gaql-query called with:", args); 52 | try { 53 | const result = await gaqlClient.executeGaqlQuery(args); 54 | return { 55 | content: [{ 56 | type: "text", 57 | text: `Query result:\n${result}` 58 | }] 59 | }; 60 | } 61 | catch (err) { 62 | console.error("❌ Error in execute-gaql-query:", err); 63 | return { 64 | content: [{ 65 | type: "text", 66 | text: `Failed to execute query: ${err.message}` 67 | }] 68 | }; 69 | } 70 | }); 71 | // Start server 72 | async function main() { 73 | console.error("🚀 Connecting to MCP server..."); 74 | const transport = new StdioServerTransport(); 75 | await server.connect(transport); 76 | console.error("✅ MCP server is running."); 77 | } 78 | main().catch((err) => { 79 | console.error("🔥 Fatal error in main():", err); 80 | process.exit(1); 81 | }); 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@trueclicks/google-ads-mcp-js", 3 | "version": "0.3.7", 4 | "description": "Gogle Ads MCP by TrueClicks enables your GPT (like Claude) to securely access and query your Google Ads account data via GAQL.app.", 5 | "license": "MIT", 6 | "author": "TrueClicks", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/TrueClicks/google-ads-mcp-js.git" 10 | }, 11 | "homepage": "https://www.trueclicks.com", 12 | "bugs": { 13 | "url": "https://github.com/TrueClicks/google-ads-mcp-js" 14 | }, 15 | "type": "module", 16 | "bin": { 17 | "google-ads-mcp-js": "build/index.js" 18 | }, 19 | "files": [ 20 | "build" 21 | ], 22 | "scripts": { 23 | "build": "npx tsc && npx shx chmod +x build/*.js", 24 | "prepare": "npm run build", 25 | "watch": "npx tsc --watch" 26 | }, 27 | "dependencies": { 28 | "@modelcontextprotocol/sdk": "^1.8.0", 29 | "zod": "^3.24.2" 30 | }, 31 | "devDependencies": { 32 | "@types/node": "^22.14.0", 33 | "typescript": "^5.8.2", 34 | "shx": "^0.4.0" 35 | }, 36 | "main": "index.js" 37 | } -------------------------------------------------------------------------------- /src/client/gaql-client.ts: -------------------------------------------------------------------------------- 1 | // src/client/gaql-client.ts 2 | 3 | const API_BASE = "https://api.gaql.app"; 4 | const USER_AGENT = "qaql-tool-client/1.0"; 5 | 6 | export interface GaqlQueryRequest { 7 | query: string; 8 | customerId: number; 9 | loginCustomerId: number; 10 | reportAggregation: string; 11 | } 12 | 13 | export class GaqlClient { 14 | private token: string; 15 | 16 | constructor(token: string) { 17 | if (!token) { 18 | throw new Error("GAQL token is required"); 19 | } 20 | 21 | this.token = token; 22 | } 23 | 24 | async getAccounts(): Promise { 25 | const url = `${API_BASE}/api/gpt/google-ads/get-accounts?gptToken=${this.token}`; 26 | 27 | const response = await fetch(url, { 28 | headers: { 29 | "User-Agent": USER_AGENT 30 | } 31 | }); 32 | 33 | if (!response.ok) { 34 | const errorText = await response.text(); 35 | throw new Error(`GAQL error ${response.status}: ${errorText}`); 36 | } 37 | 38 | return response.json(); 39 | } 40 | 41 | async executeGaqlQuery(query: GaqlQueryRequest): Promise { 42 | const url = `${API_BASE}/api/gpt/google-ads/execute-query?gptToken=${this.token}`; 43 | 44 | const response = await fetch(url, { 45 | method: "POST", 46 | headers: { 47 | "Content-Type": "application/json", 48 | "User-Agent": USER_AGENT 49 | }, 50 | body: JSON.stringify(query) 51 | }); 52 | 53 | if (!response.ok) { 54 | const errorText = await response.text(); 55 | throw new Error(`GAQL error ${response.status}: ${errorText}`); 56 | } 57 | 58 | const json = await response.json(); 59 | return JSON.stringify(json, null, 2); // pretty print 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 4 | import { z } from "zod"; 5 | import { GaqlClient, GaqlQueryRequest } from "./client/gaql-client.js"; 6 | 7 | // CLI token parsing 8 | console.error("Process argv:", process.argv); 9 | const tokenArg = process.argv.find(arg => arg.startsWith("--token=")); 10 | const token = tokenArg?.substring("--token=".length); 11 | 12 | if (!token) { 13 | console.error("❌ Error: Missing --token argument."); 14 | process.exit(1); 15 | } else { 16 | console.error("✅ Parsed token."); 17 | } 18 | 19 | const gaqlClient = new GaqlClient(token); 20 | 21 | const server = new McpServer({ 22 | name: "Google Ads MCP", 23 | version: "1.0.0" 24 | }); 25 | 26 | // Tool: get-accounts 27 | server.tool( 28 | "get-accounts", 29 | "Gets Google Ads accounts.", 30 | async () => { 31 | console.error("🛠 get-accounts called"); 32 | try { 33 | const accounts = await gaqlClient.getAccounts(); 34 | return { 35 | content: [{ 36 | type: "text", 37 | text: `Accounts:\n${JSON.stringify(accounts, null, 2)}` 38 | }] 39 | }; 40 | } catch (err: any) { 41 | console.error("❌ Error in get-accounts:", err); 42 | 43 | return { 44 | content: [{ 45 | type: "text", 46 | text: `Failed to fetch accounts: ${err.message}` 47 | }] 48 | }; 49 | } 50 | } 51 | ); 52 | 53 | // Tool: execute-gaql-query 54 | server.tool( 55 | "execute-gaql-query", 56 | "Executes a GAQL query and returns the result as a formatted JSON string.", 57 | { 58 | query: z.string().describe("GAQL query"), 59 | customerId: z.number().describe("Customer ID"), 60 | loginCustomerId: z.number().describe("Login Customer ID"), 61 | reportAggregation: z.string().describe("Report aggregation (for Microsoft Advertising only)") 62 | }, 63 | async (args: GaqlQueryRequest) => { 64 | console.error("🛠 execute-gaql-query called with:", args); 65 | try { 66 | const result = await gaqlClient.executeGaqlQuery(args); 67 | 68 | return { 69 | content: [{ 70 | type: "text", 71 | text: `Query result:\n${result}` 72 | }] 73 | }; 74 | } catch (err: any) { 75 | console.error("❌ Error in execute-gaql-query:", err); 76 | 77 | return { 78 | content: [{ 79 | type: "text", 80 | text: `Failed to execute query: ${err.message}` 81 | }] 82 | }; 83 | } 84 | } 85 | ); 86 | 87 | // Start server 88 | async function main() { 89 | console.error("🚀 Connecting to MCP server..."); 90 | const transport = new StdioServerTransport(); 91 | await server.connect(transport); 92 | console.error("✅ MCP server is running."); 93 | } 94 | 95 | main().catch((err) => { 96 | console.error("🔥 Fatal error in main():", err); 97 | process.exit(1); 98 | }); 99 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } --------------------------------------------------------------------------------