MCP server by santisanti13
ads-waste-auditor-mcp
An MCP (Model Context Protocol) server that audits ad spend across platforms (Google Ads, Meta Ads, or any source aggregated via Windsor.ai) for wasted budget — and lets Claude reason about what to do about it, not just report it.
Most paid media tooling stops at dashboards and CSV exports. This MCP goes a step further: it scans the account, flags entities (keywords, audiences, creatives, ad groups) burning budget with no return, simulates the impact of pausing them, and can pause them directly — with a reason logged for every action.
Architecture
User (natural language) → Claude → Your MCP Server → PostgreSQL
│ (ad_campaigns,
┌───────┴───────┐ ad_entities,
│ Audit Layer │ ad_metrics_daily)
│ (SQL + biz │ ▲
│ logic) │ │
└───────┬───────┘ Windsor.ai sync
│ (or direct
audit → simulate → pause platform APIs,
in production)
The audit layer doesn't just run raw SELECTs — it joins campaigns + entities +
daily metrics, computes account-wide CPA, and flags outliers against
configurable thresholds. pause_entity is the only tool that mutates state,
and it requires a reason, creating an audit trail for every action taken.
Tools
| Tool | Description |
|------|-------------|
| audit_account_waste | Scans all campaigns/entities over N days, flags wasted spend (zero conversions or CPA far above account average), returns a prioritized report with total € wasted and % of account spend |
| get_waste_detail | Day-by-day breakdown for a single entity — why it was flagged |
| simulate_reallocation | Read-only: projects how much budget would be freed (daily/monthly) and how many conversions would be lost if given entities were paused — run before acting |
| pause_entity | Action: pauses a specific entity, requires a reason for audit-trail purposes |
Recommended flow: audit_account_waste → get_waste_detail (optional) →
simulate_reallocation → pause_entity
Data model
CREATE TYPE ad_platform AS ENUM ('google_ads', 'meta_ads');
CREATE TYPE entity_type AS ENUM ('campaign', 'ad_group', 'keyword', 'audience', 'creative');
CREATE TYPE entity_status AS ENUM ('active', 'paused');
CREATE TABLE ad_campaigns (
id serial PRIMARY KEY,
platform ad_platform NOT NULL,
name text NOT NULL,
daily_budget_cents integer NOT NULL,
status entity_status NOT NULL DEFAULT 'active',
objective text
);
CREATE TABLE ad_entities (
id serial PRIMARY KEY,
campaign_id integer REFERENCES ad_campaigns(id),
entity_type entity_type NOT NULL,
name text NOT NULL,
status entity_status NOT NULL DEFAULT 'active'
);
CREATE TABLE ad_metrics_daily (
id serial PRIMARY KEY,
entity_id integer REFERENCES ad_entities(id),
date date NOT NULL,
impressions integer NOT NULL DEFAULT 0,
clicks integer NOT NULL DEFAULT 0,
spend_cents integer NOT NULL DEFAULT 0,
conversions integer NOT NULL DEFAULT 0
);
In production this would be populated by a Windsor.ai sync (or direct platform APIs) on a schedule — the tools are platform-agnostic as long as this shape is maintained, so the same MCP works whether the underlying spend is Google Ads, Meta Ads, LinkedIn Ads, TikTok Ads, etc.
Setup
1. Install dependencies
git clone https://github.com/santisanti13/ads-waste-auditor-mcp.git
cd ads-waste-auditor-mcp
npm install
2. Environment variables
DATABASE_URL=postgresql://user:pass@host:5432/db
PORT=3001
3. Build and run
npm run build
npm start
The server starts at http://localhost:3001/mcp.
Connect to Claude
Add to Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"ads-waste-auditor": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://localhost:3001/mcp"]
}
}
}
Usage example
User: Audit my ad account for wasted spend in the last 30 days
Claude calls: audit_account_waste({ days: 30 })
→ Returns: {
total_account_spend_eur: 18255.84,
total_wasted_eur: 10705.63,
waste_pct: 58.6,
flagged: [
{ entity_id: 5, name: "edtech tools 2026", spend_cents: 157048,
conversions: 0, reason: "0 conversions over 30 days" },
{ entity_id: 4, name: "best lms software", spend_cents: 151433,
conversions: 1, cpa_cents: 151433, reason: "65.7x account avg CPA" },
...
]
}
Claude presents this as a prioritized table and explains that 5 of 7 flagged
entities sit in one campaign ("Search - Generic EdTech"), suggesting it's
mistargeted with broad-match keywords.
User: Yes, simulate pausing those 7
Claude calls: simulate_reallocation({ entity_ids: [3,4,5,6,7,10,13], days: 30 })
→ Returns: {
total_freed_monthly_eur: 10705.63,
total_conversions_lost_per_month: 4.5,
recommendation: "Some conversions would be lost — weigh against where
this budget could be redirected."
}
User: Pause entity 5, it's the clearest case
Claude calls: pause_entity({
entity_id: 5,
reason: "€1,570.48 spent over 30 days with 0 conversions"
})
→ Returns: { entity_id: 5, previous_status: "active", new_status: "paused", ... }
Tech stack
- Node.js + TypeScript
@modelcontextprotocol/sdk— MCP serverpg— PostgreSQL clientexpress— HTTP transport (Streamable HTTP)
Notes
pause_entityis the only tool that mutates state (ad_entities.status = 'paused'). It requires areasonargument so every action has an audit trail.simulate_reallocationis read-only and should always be run beforepause_entityto project the impact of a change.- This is a companion project to
postgres-mcp-server, a general-purpose Postgres CRUD MCP. They run as separate servers and can point at the same or different databases.
About
MCP-based ad spend auditor: detects wasted budget across Google Ads / Meta Ads (via Windsor.ai-shaped data), simulates reallocation, and pauses underperforming entities with full audit trail.
Author
Built by Santi — SaaS builder, EdTech & GovTech.