PerfoAds MCP

com.perfoads由 perfoads.com 发布v0.2.0-alpha.1更新于 Nov 8, 2026

Checks your Google Ads accounts (162 checks) and changes them only after you approve. Self-hosted.

已验证STDIO仅桌面数据与分析商业与电商

概览

AI 生成的概览

自托管的 MCP 服务器,让助手读取 Google Ads 账户、运行 162 项账户检查,并且只在人工批准后修改账户。

可以做什么
  • 检查安装环境并列出可访问的 Google Ads 账户,包括经理账户下的账户
  • 读取账户概览、广告系列、广告组、关键词、搜索词、预算、否定关键词、广告、受众、转化和更改历史
  • 执行只读 GAQL 查询并查看资源字段说明
  • 生成快照并运行 162 项确定性检查,给出问题、证据和严重程度
  • 提议、批准、应用、撤销并跟踪对预算、状态、出价、否定词、关键词和广告的更改
适用场景
适合希望让助手审计 Google Ads 账户、说明哪些花费被浪费,并在明确批准下做有限修改,同时数据不经过第三方服务器的场景。
运行要求
通过 npx 在本地运行(需 Node.js 22.13 或更高版本)。需要自己的 Google Cloud 项目并启用 Google Ads API、一个“桌面应用”类型的 OAuth 客户端,以及已获批的 Explorer 访问权限;获批前只能使用测试账户。可选环境变量 PERFOADS_MCP_HOME 指定设置文件夹。需要访问 Google API 的网络。
安装前请注意
会在本地设置文件夹中加密保存 OAuth 客户端 ID、密钥和刷新令牌;同一操作系统用户下的任何进程都能读取密钥,若助手可访问终端或浏览器,应使用加固配置。账户未写入 policy.yaml 的 accounts.write 前无法修改;批准需在本地页面输入代码,AI 说“同意”不算批准。auto 级别可不逐次批准即暂停广告系列或降低预算。目前为早期 alpha 版本。

安装

在 SourceWeft 中

  1. 打开 控制台中的 PerfoAds MCP,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

[PerfoAds MCP]

PerfoAds MCP

A self-hosted MCP server that works with Google Ads. It lets an AI assistant (Claude Desktop, Claude Code, Cursor and other MCP apps) read your Google Ads accounts, run 162 deterministic account checks, and change things only with your approval and within limits you set in a policy file.

PerfoAds MCP works with Google Ads. It is not made, endorsed or supported by Google (not a Google product). It works with the Google Ads API through your own Google Cloud project and your own sign-in; nothing goes through anyone else's servers.

Package and CLI: perfoads-mcp. License: Apache-2.0. Status: early alpha. On npm: perfoads-mcp (0.2.0-alpha.1).

Work with Paweł

The checks say what is wrong: the evidence, how serious it is and the money it wastes. They do not say how to fix it. Want fixes, not just findings? PerfoAds adds fix plans; Paweł Wilk offers a free 1-hour call - perfoads.com

The safety promise

No change can be applied through MCP without a human approving it outside the model. On hosts where the agent also has a shell or controls the browser (Claude Code, Cursor, browser-control tools) this holds only with the hardened setup (server + store under a separate OS user or in a container, approval_mode: code_only and open_browser: false), and only if you do the human steps (your password, approving, policy edits) from a session the AI can't change: another account's login session, SSH from another device, or with the AI agent sandboxed. An AI with a shell on your user can change your shell startup files, fake sudo and capture what you type.

The one exception is one you make yourself: the auto tier in your policy file (off unless you set it, and only for pausing a campaign or lowering a budget).

What this means in practice:

  • Changes are off until your policy file lists an account under accounts.write.
  • Google checks the change first without applying it (validate only). Then it is shown to you on a local approval page with the account name, id and currency first, the exact change, every campaign it reaches, and the AI's reason in a separate "unverified" box.
  • You approve with the 6-character code from that page (typed into your AI app's form or a terminal), or a click on the page in apps without forms. An AI saying "yes" approves nothing.
  • The server applies the change once, reads it back from Google, and logs every step in a hash-chained audit log. Most changes can be undone within 30 days (as a new approved change); see the per-type table.

Read docs/THREAT-MODEL.md for what this protects against and what it does not.

Requirements

  • Node.js 22.13 or later (it uses the built-in node:sqlite).
  • Your own Google Cloud project with the Google Ads API enabled.
  • An OAuth client of type "Desktop app" in that project.
  • Explorer access to the Google Ads API, applied for in Google Cloud Console for that project. Until it is approved, only Google Ads test accounts work. No developer token is needed.

Step by step: docs/SETUP.md.

Install and connect

Always name the exact version (@0.2.0-alpha.1, not @latest or none), so npm can't hand you a different release. init asks for your OAuth client id and secret, opens Google's sign-in in your browser, saves the sign-in encrypted, writes a read-only starter policy, runs doctor, and prints the config for your AI app.

Or build from source:

sh
git clone https://github.com/vilovibe/perfoads-mcp.gitcd perfoads-mcppnpm install --frozen-lockfile && pnpm buildnode packages/cli/bin/perfoads-mcp.js init

init then prints your AI app's config for that copy (full path to node and to packages/cli/bin/perfoads-mcp.js). Wherever these docs say npx perfoads-mcp@<version> <command>, run that node command instead.

The AI app config (init prints it with your version filled in; for a source build it prints the node form):

Claude Desktop (Settings > Developer > Edit Config, claude_desktop_config.json), Cursor (~/.cursor/mcp.json):

json
{  "mcpServers": {    "perfoads-mcp": {      "command": "npx",      "args": ["-y", "perfoads-mcp@<version>", "serve"]    }  }}

Claude Code:

sh
claude mcp add --scope user perfoads-mcp -- npx -y perfoads-mcp@<version> serve

Or as a Claude Code plugin (it pins the release version; claude-plugin/):

/plugin install perfoads --marketplace vilovibe/perfoads-mcp

ChatGPT desktop app and Codex: they run local MCP servers too; see docs/SETUP.md.

More: examples/ (Claude Desktop, Claude Code, Cursor, a starter policy). A Claude Desktop extension (.mcpb) can be built with node scripts/build-mcpb.mjs; run init in a terminal first, the extension uses the same settings folder.

Then ask your AI app: "run doctor", "list my Google Ads accounts", "audit account 123-456-7890".

Quick tour

What to ask your AI, step by step: docs/USING.md. Full list with every input: docs/TOOLS.md (generated from the server).

  • Setup and accounts: doctor (checks the setup and says what to fix), list_accounts (incl. accounts under manager accounts).
  • Reads: get_account_overview, get_campaigns, get_ad_groups, get_keywords, get_search_terms, get_budgets_and_bidding, get_negatives, get_ads_and_assets, get_audiences, get_conversions, get_change_history, keyword_ideas, describe_resource, and query_gaql (one read-only GAQL SELECT). Summary first, then one page of rows and a cursor. Money is a decimal string in the account currency.
  • Checks: build_snapshot reads what the checks need into a local snapshot, run_checks runs them (findings with evidence; "not run" with the reason, never "clean" when data was missing), explain_check. Prompt weekly_audit. Catalog: docs/CHECKS.md.
  • Changes: propose_change, apply_change, undo_change, get_change, list_changes, resolve_change. Types: budgets, campaign status, bidding targets, negatives (campaign and shared lists), keywords, pausing keywords, responsive search ad text, sitelinks, callouts, audience exclusions, and creating a new Search campaign from plain fields (always made paused; undo only while it never ran).

Text that comes from Google or the AI (names, search terms, ad texts, errors) is wrapped as {"untrusted": "..."} so the AI treats it as data, not instructions.

How a change works

  1. Propose - the AI suggests a change. The server reads the live values, checks your policy, and asks Google to validate it without changing anything.
  2. Approve - the server opens a local approval page (127.0.0.1, one-time link, 10 minutes), or, with open_browser: false, you open it yourself with approvals open <id> in a terminal. You approve with its code, or reject.
  3. Apply - sent once. If the account changed since you approved, nothing is sent (stale). An unclear answer from Google is never re-sent: it is re-checked by reading the account.
  4. Read back - the server reads the account again to confirm the change landed (verified), or says loudly that it did not.
  5. Log and undo - every step is in the audit log (changes://log); undo_change proposes the reverse as a new change, with its own approval.

Approval paths and approval_mode: see docs/HARDENED-SETUP.md.

The policy file

policy.yaml in the settings folder decides which accounts the AI may read and change and how far: per-account spend limits in the account's currency, budget and target step limits, writes per minute and day, tiers per change type (auto, confirm, blocked), brand terms that are never negated, a Google Ads operations reserve for changes, and a kill switch. It is re-read on every check, and no tool can edit it.

sh
npx perfoads-mcp@<version> policy check   # is it valid, and what does it allow?npx perfoads-mcp@<version> policy show    # what the server uses now

Every key, range and default, with examples: docs/POLICY.md. Starter file: examples/policy.example.yaml.

What is stored, and how to delete it

Everything lives in one settings folder, never in your project: ~/.config/perfoads-mcp (or $XDG_CONFIG_HOME/perfoads-mcp), %APPDATA%\perfoads-mcp on Windows. Change it with PERFOADS_MCP_HOME, but keep it outside any folder the AI works in (doctor checks).

  • store.sqlite - your OAuth client id, client secret and refresh token (each encrypted, AES-256-GCM), the list of your accounts, proposed and applied changes, approval sessions (codes only as hashes), the audit log, Google Ads operations used, and every policy version loaded.
  • snapshots/ - account snapshots for the checks (gzipped JSON, owner-only, kept 24 hours, at most 20). They hold account data such as campaign names and search terms.
  • policy.yaml - your change policy. config.json - optional approval_mode, open_browser, retention_days and pro_plugin (an absolute path to a separately installed plugin package, loaded at start; a plugin runs as code with the server's rights, so set it only for a package you trust; it is refused when anyone but the user running the server, or root, can change its files or a folder above them; its text comes back wrapped as untrusted; doctor shows whether it loaded and why not).
  • Kept for 180 days by default ("retention_days" in config.json, 30 to 3650): when the server starts, changes in a final state (verified, failed, rejected, expired, stale, settled, abandoned), finished approval sessions, policy versions nothing still refers to, and the oldest audit log entries, all older than that, are deleted. Unfinished changes (proposed, approved, applying, in doubt, applied but not yet checked) and anything they need are never deleted. Before deleting, the server writes a retention_pruned entry to the audit log with what it removes and a checkpoint (the number and hash of the last entry removed), so the rest of the log still verifies. Snapshot temp files from interrupted saves go after 1 hour.
  • The encryption key: macOS Keychain, the Linux keyring (secret-tool), or a key file (key) in the settings folder (always the key file on Windows).
  • Owner-only, per OS: on macOS and Linux the store files, the key file, snapshot files and the starter policy.yaml are created 0600 and snapshot folders 0700 (a config.json you write yourself keeps the mode you give it). On Windows file modes do nothing, so each time the store opens the server sets an ACL on the whole settings folder with %SystemRoot%\System32\icacls.exe: inheritance from the parent removed, full control for your Windows user only, applied to everything already in it and inherited by everything created later (store, key file, snapshots, policy.yaml, config.json); the key file, snapshot folders and files and the starter policy get it again when they are written. doctor warns on Windows when the settings folder is outside %APPDATA%, and fails (with the fix) when icacls could not make it owner-only.

Tokens are encrypted at rest and never written to logs or shown in tool results. Encryption protects a copy of the folder (a backup, a synced folder). It does not protect against programs running as your own OS user: the Keychain entry, the keyring entry and the key file can be read by any process of the same user without a prompt, including an AI agent with a terminal. The hardened setup closes that, within its stated limits (human steps from a session the AI can't change).

Disconnect and delete: npx perfoads-mcp@<version> reset asks Google to revoke the saved sign-in (best effort; it says whether Google confirmed), then deletes the sign-in and its key (it asks first). Delete the settings folder to remove everything else. If reset says Google did not confirm, remove the app's access at myaccount.google.com/permissions.

Nothing is sent anywhere except to Google's APIs. No telemetry.

Commands

perfoads-mcp init                    guided setupperfoads-mcp auth                    sign in again (uses the saved OAuth client)perfoads-mcp doctor                  check the setupperfoads-mcp reset                   delete the saved sign-in and its key (asks first)perfoads-mcp serve                   run the MCP server over stdio (AI apps start this)perfoads-mcp approve <change_id>     approve with the code from the approval page (terminal only)perfoads-mcp reject <change_id>      reject a proposed change (terminal only)perfoads-mcp approvals open <id>     show a change's approval page from this terminalperfoads-mcp policy check [file]     check a policy file and say what it allowsperfoads-mcp policy show             the change policy the server uses now

Use npx perfoads-mcp@<version> <command> (or the command init printed) if it is not installed globally.

Known limits

  • Alpha. The Google Ads queries and writes are tested against recorded and fake replies. Live runs passed on a Google Ads test account (2026-10-25, every change type with its undo) and read-only on 4 real accounts (2026-10-26), and every change type with its undo on a real account (2026-11-01, $0 spent). Coverage per change type: docs/E2E-COVERAGE.md.
  • Same OS user, no hardened setup: an AI with a shell can read the store and key, start its own copy of the server, edit the policy, and fake a terminal (approve and approvals open are a convenience, not a guard). An AI that controls your browser can read the approval page, code included. Until you open it, the page link is visible for a moment in the browser opener's command line (ps) and stays in browser history; it stops working after the first open.
  • Access levels: Explorer access allows 2,880 operations a day on live accounts (15,000 on test accounts, counted separately) and no Keyword Planner (keyword_ideas needs Basic or Standard). The server keeps a share of each day's operations for approved changes.
  • OAuth app in "Testing" mode: Google ends the sign-in after 7 days. init and doctor warn; publish the app to production to avoid it (docs/SETUP.md).
  • Unclear outcomes: changes to portfolio bidding strategies and shared negative lists can't be settled from Google's change history; after an unclear answer they stay in_doubt until the new value shows, you settle them (resolve_change), or 30 days pass.
  • Change history reads at most 28 days. Every tool answers within about 4 minutes; very large accounts get samples, marked as such, and checks on a sample never say "clean".
  • Not supported yet: ChatGPT on the web (needs a remote server; the ChatGPT desktop app and Codex work), remote or team use, Merchant Center, billing.

Privacy Policy

PerfoAds MCP runs on your computer. Your Google Ads data goes only between your computer and Google; nothing is sent to PerfoAds, and there is no telemetry. What is stored locally, for how long, and how to delete it: What is stored, and how to delete it. Full policy: https://perfoads.com/privacypolicy

Terms of service: https://perfoads.com/termsofservice (the PerfoAds MCP section: free, as is, no warranty, Apache-2.0, your own Google account and costs, you approve every change).

Contact: [email protected] for privacy and security questions (security reports: see SECURITY.md). Bugs and support: https://github.com/vilovibe/perfoads-mcp/issues

Docs

Packages: ads-client, reads, snapshot, checks, changes, policy, store, mcp-server, cli (see packages/). Contributing: CONTRIBUTING.md.

Licence

Apache License 2.0. See LICENSE and NOTICE.

  • You are free to use, change, share and sell it, for any purpose.
  • Keep the licence and the NOTICE file with any copy you share, and say what you changed.
  • It comes with no warranty.

Google Ads is a trademark of Google LLC.

来源:README.md,提交 ce171c8

工具

25

doctor

Check setup

Checks the setup step by step: Node.js, where settings are stored, the saved Google sign-in, Google Ads API access level and limits, and risky Claude Code hooks. Each line says ok, warn, fail or skipped in plain English, with a fix; exact commands and file paths are in each check's details.

无需输入参数。

list_accounts

List Google Ads accounts

Lists every Google Ads account this sign-in can work on, including accounts under manager (MCC) accounts, with currency, time zone and the login-customer-id each one needs.

输入参数refresh

query_gaql

Run a read-only GAQL query

Runs one Google Ads Query Language SELECT on one account. Read-only: anything but a single SELECT on a known resource is refused. LIMIT is added (200) or lowered (max 1000). Returns a summary first, then up to max_rows rows; next_cursor fetches the rest. Costs 1 Google Ads operation per call.

输入参数query必填cursormax_rowscustomer_id必填

get_account_overview

Account overview

One account at a glance: name, currency, time zone, totals for the date range (cost, clicks, conversions, value, ROAS), campaigns per channel. Example: {"customer_id":"123-456-7890"}

输入参数date_rangecustomer_id必填

get_campaigns

Campaigns

Campaigns that are not removed, plus removed ones that had activity in the date range: status, channel, budget, bidding, targets, metrics, Search impression share; highest cost first. Example: {"customer_id":"123-456-7890","date_range":"LAST_7_DAYS"}

输入参数cursorstatusmax_rowsdate_rangecampaign_idcustomer_id必填

get_ad_groups

Ad groups

Ad groups (not removed): status, type, CPC bid, metrics; highest cost first. Example: {"customer_id":"123-456-7890","campaign_id":"111"}

输入参数cursorstatusmax_rowsdate_rangead_group_idcampaign_idcustomer_id必填

get_keywords

Keywords

Keywords with impressions: match type, quality score parts, bids, page CPC estimates, metrics; highest cost first. Example: {"customer_id":"123-456-7890","ad_group_id":"222"}

输入参数cursorstatusmax_rowsdate_rangead_group_idcampaign_idcustomer_id必填

get_search_terms

Search terms

Search terms with clicks (Search + Performance Max) with triggering keyword, status and metrics; highest cost first; cost on terms with no conversions. Example: {"customer_id":"123-456-7890","date_range":"2026-09-01..2026-09-30"}

输入参数cursormax_rowsdate_rangead_group_idcampaign_idcustomer_id必填include_pmax_categories

get_budgets_and_bidding

Budgets and bidding

Budgets with the campaigns sharing each, and portfolio bid strategies with their targets and campaigns: what a budget or strategy change touches. Example: {"customer_id":"123-456-7890"}

输入参数cursormax_rowscustomer_id必填

get_negatives

Negatives

Negative keywords on ad groups and campaigns, shared negative lists (keywords, links) and account-level negatives and exclusions. Example: {"customer_id":"123-456-7890"}

输入参数cursormax_rowscustomer_id必填

get_ads_and_assets

Ads and assets

Ads (RSA texts with pinning and labels, URLs, ad strength, policy) and asset links (sitelinks, callouts, snippets, images...) on account, campaigns, ad groups. Example: {"customer_id":"123-456-7890","kind":"ads"}

输入参数kindcursorstatusmax_rowsdate_rangead_group_idcampaign_idcustomer_id必填

get_audiences

Audiences

User lists (type, status, size, targeted or not) and audience targeting/exclusions on campaigns and ad groups, with metrics. Example: {"customer_id":"123-456-7890"}

输入参数kindcursormax_rowsdate_rangead_group_idcampaign_idcustomer_id必填

get_conversions

Conversions

Conversion actions (status, category, primary, counting, attribution, values) with conversions in the range; account/campaign goals; value rules. Example: {"customer_id":"123-456-7890"}

输入参数cursormax_rowsdate_rangecustomer_id必填

get_change_history

Change history

Who changed what, when (1-28 days; Google keeps 30): resource, operation, fields, tool; users as "user at domain". Example: {"customer_id":"123-456-7890","days":7}

输入参数dayscursormax_rowscampaign_idcustomer_id必填resource_type

keyword_ideas

Keyword ideas

Keyword Planner ideas from seed keywords and/or a URL: searches, competition, bids. Needs Basic/Standard access. Example: {"customer_id":"123-456-7890","keywords":["trail running shoes"]}

输入参数urlkeywordslanguagecustomer_id必填max_resultsgeo_target_idsinclude_search_partners

describe_resource

Describe a GAQL resource

Fields of one GAQL resource from Google's field catalogue (type, selectable, filterable, sortable, enum values) plus the metrics and segments you can select with it. Example: {"resource":"campaign"}

输入参数cursormax_rowsresource必填

build_snapshot

Build account snapshot

Reads everything the checks need (90 days) into a saved snapshot (kept 24 h; a complete one under 60 min old is reused unless refresh:true). Returns its id and how each read went; failed or cut reads are said. Then run_checks. Example: {"customer_id":"123-456-7890"}

输入参数checksrefreshcustomer_id必填

run_checks

Run account checks

Runs the checks on a snapshot (snapshot_id) or on customer_id (reuses one under 60 min old unless refresh). Findings first, with evidence and severity, paged by next_cursor; after the last finding: info (not faults), clean, not_run (why) and suppressed (why). For an audit, call again with next_cursor until it is null and report every finding in the audit report format (server instructions, weekly_audit prompt), never only the main points. A keyword id looks like "<ad_group_id>~<criterion_id>"; pause_keyword takes it as two fields (ad_group_id, criterion_id). Example: {"snapshot_id":"snap_..."}

输入参数cursordetailrefreshsettingsbrand_termscustomer_idsnapshot_idclient_context

explain_check

Explain a check

What one check looks for, the data it needs, its settings with defaults, and when it cannot run. Tell the user in plain words what it looks for and what it needs; do not suggest settings overrides or how to fix things. Example: {"check_id":"BB02"}

输入参数check_id必填

propose_change

Propose a change

Builds one change, checks it with Google (validate only), never applies. params schema per type: schema://changes/{type}. Returns diff, risk, change_id; then apply_change.

输入参数type必填params必填reasoncustomer_id必填

apply_change

Apply an approved change

Sends a proposed change once a person approves it: opens an approval page in the user's browser (approve there, or type its code when asked), sends once, reads back.

输入参数change_id必填

undo_change

Propose an undo

Proposes a change restoring the values from before an applied change. Needs its own approval via apply_change.

输入参数change_id必填

get_change

Get a change

One change: status in plain words, diff, approval, result. An in_doubt change is re-checked by reading.

输入参数change_id必填

list_changes

List changes

Recent changes, newest first: id, type, status in plain words, account, a one-line summary and times. get_change shows one change in full.

输入参数limitstatuscustomer_id

resolve_change

Settle an unclear change

Settles an in_doubt change after the user checked Google Ads (outcome + note). A person confirms on the approval page or types its code. Sends nothing.

输入参数note必填outcome必填change_id必填

版本历史

1
  1. v0.2.0-alpha.1最新Nov 8, 2026

评分

已满足适用检查项的 90%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分
  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具必需15 分
  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分