paid-media-mcp
Provides access to Amazon Ads campaign data, performance metrics, and account structure for cross-channel analysis and reporting.
Connects to Google Ads to query campaign data, performance history, and account information across the paid media program.
Integrates with Meta Ads to retrieve campaign data, audience insights, and institutional knowledge for unified analysis.
Enables accessing TikTok Ads campaign data and performance metrics within the same context as other platforms.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@paid-media-mcpShow me last week's top performing campaigns across all channels"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Part of the Paid Media AI Suite
This is one component of a three-part system. See paid-media-agent for the full architecture, setup guide, and AGENT.md.
Component | Role |
Autonomous agents + BigQuery schema DDL — Watchdog, Analyst, Operator on Cloud Run; schema in | |
paid-media-mcp ← you are here | Interactive data server — connects Claude to campaign data and agent outputs |
Interactive skill library — 16+ paid-media skills for Claude Code |
Paid Media MCP
A Model Context Protocol (MCP) server template for paid media teams. Connect Claude to your campaign data, team structure, performance history, attribution models, reporting templates, asset library, test-and-learn history, audience library, and measurement setup — so it can answer questions, write reports, debug tracking issues, and assist with analysis directly in your workflow. This is a platform agnostic MCP combining campaign data, institutional knowledge and historical performance across current and past vendors.
This is a template. The example data is for a fictional e-commerce company ("Acme Corp"). You replace it with your own.
Related MCP server: synter-mcp-server
The paid media agent suite
This MCP is one part of a three-piece toolkit for a Claude-powered paid media workflow:
Component | What it does |
paid-media-mcp ← you are here | MCP server — connects Claude to your campaign data, team structure, performance history, and institutional knowledge |
Setup wizard and data import skills — populate your data files from BigQuery, spreadsheets, or platform exports, and keep them current | |
Campaign strategy and execution skills — DV360, DCO, PPC, CM360 click trackers, and more. Work best when this MCP is connected. |
How they fit together:
Clone this repo and run the
/paid-media/setupskill to populate your data filesUse
/paid-media/import-dataon a regular cadence to keep campaign and performance data currentUse
/paid-media/*skills for day-to-day campaign work — they use your MCP data automatically when it's connected
Table of Contents
What this does
Platform-agnostic by design
Most AI integrations for paid media are built by the platforms themselves — Google's MCP gives Claude knowledge of Google Ads, Meta's MCP gives Claude knowledge of Meta. That's useful, but it means Claude only sees one slice of your program at a time and can't answer cross-channel questions.
This MCP is different. It is platform-agnostic: it connects Claude to your entire paid media operation, regardless of which channels, DSPs, networks, or agencies you use. Claude gets a unified view of every platform you run, every team that manages them, and every dollar you spend — all at once.
Three layers of context in one connection
1. Live ad platform data Every campaign across every channel — Meta, Google Ads, DV360, SA360, LinkedIn, TikTok, Amazon, and more. Budgets, objectives, targeting, performance metrics, and status all in one place. No more switching between platform UIs to answer a basic question.
2. Institutional knowledge Information that lives in spreadsheets, Confluence pages, agency decks, and people's heads — captured once and always available:
How your team is structured and who owns what
Your attribution rules and how you measure conversions
Your audience strategy: first-party segments, data providers, lookalike approach
Your tracking setup: tag management, pixels, Conversion APIs, CM360 u-variables
Your asset library: where files live, naming conventions, per-platform specs
Your test-and-learn history: what you've tried, what worked, and why — across not just creative tests but DSP evaluations, agency reviews, ad network trials, and tool assessments
3. Historical data Performance records over time, benchmarks by platform and objective, and completed test results. Claude can compare current performance against your own historical baseline — not just generic industry averages.
What this means in practice
Without this, Claude gives generic paid media advice. With it, Claude gives advice that is specific to your program, your data, and your institutional history:
"Your Q2 Meta prospecting campaign is running 2.4x ROAS against your 4.0x target. Retargeting is at 12.3x — the prospecting pool may be too cold. Based on your April LAL test, tightening to 1% and adding the purchaser suppression list drove 18% lower CPA. That's the move here."
"You ran a DSP evaluation last year. DV360 beat The Trade Desk on CPA by 11%, but the note says TTD's frequency management advantage will grow as cookies deprecate. It's been 14 months — worth re-running that test."
"Your social agency contract is up in Q4. The evaluation from 2025 showed Social Natives outperformed Spark & Co. on CPA by 18% and 3x creative output. That history is already here if you want to use it in the RFP process."
These answers are only possible when campaign data, institutional knowledge, and historical records exist in the same context — which is exactly what this MCP provides.
Quick start
Prerequisites
Claude Desktop (for connecting Claude)
Git
Step 1 — Clone and install
After cloning, run
bash scripts/install-hooks.shonce to install the pre-commit guard against committing private assets.
git clone https://github.com/arcticgreyy/paid-media-mcp.git
cd paid-media-mcp
npm installStep 2 — Fill in your data
Edit the JSON files in the data/ folder. Each file has detailed examples. See the Filling in your data section for field-by-field guidance.
At minimum, fill in:
data/metadata.json— your company namedata/accounts.json— your ad accountsdata/teams.json— your team(s)
The server will start even if other files are missing or partially filled.
Step 3 — Build
npm run buildFor development without building (requires tsx):
npm run devStep 4 — Connect to Claude Desktop
Open your Claude Desktop config file:
Mac:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
Add the following (replace the paths with your actual absolute paths):
{
"mcpServers": {
"paid-media": {
"command": "node",
"args": ["/absolute/path/to/paidmedia-mcp/dist/index.js"],
"env": {
"PAID_MEDIA_DATA_DIR": "/absolute/path/to/paidmedia-mcp/data"
}
}
}
}To find your absolute path, run this from the project folder:
pwdCopy the output and append /dist/index.js for the args value and /data for PAID_MEDIA_DATA_DIR.
Step 5 — Restart Claude Desktop
Fully quit and reopen Claude Desktop. You should see a hammer icon (🔨) in the chat input — click it to verify the paid-media tools are listed.
Updating data
After editing files in data/, restart Claude Desktop (or the MCP server process) for changes to take effect. The server loads data files at startup.
Filling in your data
metadata.json
Basic company info. Used in report headers and resource descriptions.
{
"metadata": {
"company_name": "Your Company Name",
"industry": "E-commerce",
"primary_currency": "USD",
"fiscal_year_start": "01-01",
"last_updated": "2026-05-31"
}
}Field | Required | Notes |
| Yes | Appears in Claude's context |
| No | Helps Claude give relevant benchmarks and advice |
| Yes | ISO 4217 code (USD, EUR, GBP, etc.) |
| Yes | MM-DD format |
| Yes | ISO date — update when you refresh data |
accounts.json
One entry per ad account. Each account belongs to exactly one team.
{
"accounts": [
{
"id": "acc_brand_gads",
"name": "Brand — Google Ads",
"platform": "google_ads",
"platform_account_id": "123-456-7890",
"team_id": "team_brand",
"status": "active",
"currency": "USD",
"timezone": "America/New_York",
"monthly_budget": 50000,
"notes": "Brand campaigns only"
}
]
}Field | Required | Notes |
| Yes | Your internal ID — used in campaigns.json and teams.json |
| Yes | Human-readable name for Claude to display |
| Yes | See platform values below |
| Yes | The actual ID shown in the platform UI (e.g. Google Ads customer ID) |
| Yes | Must match an |
| Yes |
|
| Yes | ISO 4217 |
| Yes | IANA timezone string |
| No | Used for pacing analysis |
| No | Free text — Claude reads this |
Platform values: google_ads · meta · dv360 · youtube · linkedin · tiktok · twitter_x · pinterest · snapchat · amazon · other
teams.json
One entry per media team. This is where you capture the strategic context Claude needs to give relevant advice.
{
"teams": [
{
"id": "team_brand",
"name": "Brand & Awareness",
"description": "Manages upper-funnel brand campaigns focused on reach and video.",
"objectives": ["awareness", "reach", "video_views"],
"primary_kpis": ["impressions", "reach", "cpm", "brand_lift"],
"account_ids": ["acc_brand_gads", "acc_brand_dv360"],
"member_ids": ["user_sarah", "user_james"],
"lead_id": "user_sarah",
"platforms": ["google_ads", "dv360", "youtube"],
"reporting_cadence": "weekly",
"budget_owner": "vp_marketing",
"notes": "Brand lift studies run quarterly. All campaigns follow brand safety guidelines."
}
]
}Field | Required | Notes |
| Yes | Referenced by accounts and campaigns |
| Yes | List from: |
| Yes | Free-text metric names — Claude uses these when reviewing performance |
| Yes | Must match IDs in |
| Yes | Must match IDs in |
| Yes | Team lead — must be in |
| Yes |
|
| No | Put key context here: targets, processes, constraints |
Tip: The notes and description fields are the most valuable for Claude. Include:
ROAS/CPA/CPL targets
Key rules or constraints (e.g. "brand safe only")
Important processes (e.g. "budget reviews every Monday")
Links or references to external docs
team-members.json
One entry per person. Members can belong to multiple teams.
{
"team_members": [
{
"id": "user_mike",
"name": "Mike Rodriguez",
"email": "mike@yourcompany.com",
"role": "director",
"team_ids": ["team_performance"],
"platform_specialties": ["google_ads", "meta"],
"responsibilities": [
"Performance team leadership",
"ROAS and CPA target setting",
"Budget allocation across channels"
],
"notes": "Primary stakeholder for attribution model decisions."
}
]
}Field | Required | Notes |
| Yes | Must match |
| Yes |
|
| Yes | Array — analysts or shared staff can be on multiple teams |
| Yes | Which platforms they work in day-to-day |
| Yes | Free-text list — Claude uses this to route questions to the right person |
| No | Useful for capturing context like certifications, agency relationships, or ownership areas |
campaigns.json
One entry per campaign. This is the largest and most important data file.
{
"campaigns": [
{
"id": "camp_perf_search_001",
"name": "Performance Search — Always On",
"platform": "google_ads",
"account_id": "acc_perf_gads",
"team_id": "team_performance",
"status": "active",
"objective": "conversions",
"budget": {
"type": "daily",
"amount": 2000,
"currency": "USD",
"pacing": "standard"
},
"start_date": "2026-01-01",
"end_date": null,
"targeting": {
"geo": ["US"],
"devices": ["desktop", "mobile"],
"keyword_themes": ["branded", "competitor", "category"]
},
"funnel_stage": "lower",
"tags": ["always-on", "search", "performance"],
"notes": "Target ROAS: 5.0x. Smart Bidding tROAS strategy."
}
]
}Budget types: daily · lifetime · monthly
Objective values: awareness · reach · traffic · engagement · video_views · lead_generation · app_installs · conversions · catalog_sales · store_visits
Funnel stages: upper · mid · lower
Status values: active · paused · ended · draft · archived
Targeting fields (all optional):
Field | Example |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Tags tip: Tags are searchable. Use them consistently for filtering — e.g. q2-2026, always-on, brand, retargeting, test.
historical-performance.json
Contains two sections: daily records and benchmarks.
Records
One entry per campaign per day. Include whatever metrics you have — rate metrics are re-calculated automatically when aggregating.
{
"records": [
{
"campaign_id": "camp_perf_search_001",
"date": "2026-05-01",
"metrics": {
"impressions": 125000,
"clicks": 4200,
"spend": 1850.00,
"conversions": 310,
"conversion_value": 12400.00
}
}
]
}Available metric fields:
Metric | Description |
| Total ad impressions |
| Total clicks |
| Total spend in account currency |
| Unique users reached |
| Average impressions per user |
| Click-through rate (%) |
| Cost per click |
| Cost per 1,000 impressions |
| Cost per acquisition/conversion |
| Return on ad spend |
| Total conversion events |
| Revenue value of conversions |
| Video view count |
| Percentage who watched |
| View-assisted conversions |
| Any additional metrics as key-value pairs |
Tip: You don't need to pre-calculate rate metrics (CTR, CPC, ROAS). The server calculates them when aggregating. Just include the raw counts.
How to export data: Most platforms let you download daily campaign performance as CSV. Use a tool like Google Sheets or Python to convert to this JSON format. See data/historical-performance.json for the full structure.
Benchmarks
Industry reference points for comparison. Optional but makes performance analysis much richer.
{
"benchmarks": [
{
"platform": "google_ads",
"objective": "conversions",
"industry": "e-commerce",
"avg_ctr": 2.69,
"avg_cpc": 1.33,
"avg_cpa": 33.52,
"avg_roas": 3.5
}
]
}Use published industry benchmarks from WordStream, Meta, Google, or your agency. Update annually.
attribution-models.json
Documents how you measure conversions — not connected to live platforms, purely descriptive. Claude uses this to explain your measurement setup and make recommendations.
{
"attribution_configurations": [
{
"id": "attr_last_click_30d",
"name": "Last Click — 30-Day",
"model": "last_click",
"window": {
"click": 30,
"view": 0,
"unit": "days"
},
"platforms_applied": ["google_ads", "meta"],
"conversion_events": ["purchase", "lead_form_submit"],
"cross_device": false,
"view_through_enabled": false,
"description": "100% credit to the last clicked ad within 30 days.",
"use_cases": [
"Bottom-funnel campaign optimization",
"Simple baseline for cross-channel comparison"
],
"notes": "Over-credits search/retargeting; undervalues prospecting."
}
]
}Model values: last_click · first_click · linear · time_decay · position_based · data_driven · custom
Tip: Add one entry per meaningful configuration you use — e.g. one for Google Ads, one for Meta, one for cross-channel analysis. The use_cases and notes fields are especially valuable for Claude's analysis.
reporting-templates.json
Defines the structure of your reports and dashboards. Claude uses these when generating reports to follow the right format for the right audience.
{
"reporting_templates": [
{
"id": "tmpl_weekly_team",
"name": "Weekly Performance Report",
"type": "performance_summary",
"audience": "media_team",
"cadence": "weekly",
"metrics_included": ["spend", "conversions", "roas", "cpa"],
"dimensions": ["campaign", "platform", "date"],
"visualizations": ["spend_by_campaign_bar", "roas_trend_line"],
"delivery_format": ["google_sheets", "slack"],
"sections": [
{
"title": "Week Summary",
"description": "Overall performance vs. prior week and vs. targets.",
"metrics": ["spend", "conversions", "roas"]
},
{
"title": "Campaign Scorecards",
"description": "One row per active campaign with pacing status.",
"metrics": ["spend", "conversions", "cpa", "roas"]
},
{
"title": "Action Items",
"description": "Specific actions to take next week.",
"metrics": []
}
]
}
]
}Audience values: executive · media_team · client · internal
Type values: performance_summary · pacing · budget_flight · audience_insights · creative_analysis · channel_mix · attribution_path · executive_summary · custom
Tip: The sections array is the most important part — it's the outline Claude follows when writing a report. Make section descriptions as specific as possible.
assets.json
Documents your creative asset library and per-platform specs. Claude uses this to answer asset questions, check spec compliance, and guide campaign trafficking.
{
"asset_library": {
"dam_system": "Bynder",
"dam_url": "https://yourcompany.bynder.com",
"access_instructions": "Request access via IT helpdesk. Login with SSO.",
"brand_guidelines_url": "https://yourcompany.bynder.com/brand-guidelines",
"copy_guidelines_url": "https://yourcompany.bynder.com/copy-guidelines",
"categories": [
{
"id": "cat_social_video",
"name": "Social Video",
"type": "video",
"location_url": "https://yourcompany.bynder.com/collections/social-video",
"naming_convention": "{brand}_{campaign}_{duration}s_{platform}_{version}",
"specs": [
{
"platform": "meta",
"format": "MP4 or MOV",
"dimensions": "1080x1080, 1080x1920, 1920x1080",
"max_file_size": "4GB",
"duration_max": "60s (Reels), 15s (Stories)",
"notes": "Include captions — 85% watch without sound"
}
]
}
]
}
}Asset type values: image · video · copy · audio · html5 · document · other
Tip: The specs array per category is especially valuable — Claude can answer "what do I need to traffic this campaign on TikTok?" directly from this data.
testing.json
Documents your entire test-and-learn program: methodology, tools, and every test across two categories:
In-campaign A/B tests — creative, audience, bidding, landing page, copy, format, and offer experiments run within or across campaigns
Vendor and partner evaluations — structured assessments of DSPs, ad networks, agencies, ad platforms, and measurement tools
Both categories live in the same tests array and are queried with the same tools. This gives Claude a unified institutional memory of every experiment and evaluation your team has run, making it the record of what works — and what doesn't — across the full paid media operation.
{
"testing_program": {
"methodology": {
"confidence_threshold": 95,
"require_stat_sig": true,
"minimum_sample_size": 1000,
"minimum_test_duration_days": 14,
"minimum_detectable_effect_pct": 10,
"winner_criteria": "≥10% lift at 95% confidence, minimum 14 days runtime. If stat sig not achieved, test is inconclusive — retain prior best practice."
},
"tools": [
{
"id": "tool_meta_ab",
"name": "Meta A/B Testing (native)",
"type": "platform_native",
"platform": "meta",
"used_for": ["Creative A/B tests", "Audience split tests"]
}
],
"tests": [
{
"id": "test_001",
"name": "UGC vs Brand Creative",
"hypothesis": "UGC content will drive lower CPA on Meta prospecting due to higher thumb-stop rate.",
"status": "completed",
"type": "creative",
"platform": "meta",
"variants": [
{ "id": "v_control", "name": "Brand Creative", "description": "Polished brand video", "is_control": true },
{ "id": "v_ugc", "name": "UGC Video", "description": "Creator-style mobile video", "is_control": false }
],
"results": {
"winner_variant_id": "v_ugc",
"stat_sig_achieved": true,
"confidence_level": 97,
"primary_metric": "CPA",
"primary_metric_lift_pct": -23,
"conclusion": "UGC drove 23% lower CPA.",
"action_taken": "Scaled UGC — producing 2 new variants monthly."
}
},
{
"id": "test_dsp_001",
"name": "DSP Evaluation: DV360 vs. The Trade Desk",
"hypothesis": "TTD's UID2.0 identity solution will deliver better prospecting efficiency than DV360 for upper-funnel display.",
"status": "completed",
"type": "dsp",
"team_id": "team_programmatic",
"start_date": "2026-01-06",
"end_date": "2026-03-28",
"vendor_context": {
"subject": "DV360 vs. The Trade Desk",
"incumbent": "DV360",
"challenger": "The Trade Desk",
"budget_tested": 120000,
"contract_value": 0,
"evaluation_criteria": ["Prospecting CPA", "Viewable impression rate", "Frequency control", "Reporting transparency"],
"stakeholders": ["Head of Programmatic", "Finance", "Legal"],
"recommendation": "Retain DV360 as primary DSP. 11% lower CPA and superior CM360 integration. Re-evaluate in 12 months as cookie deprecation progresses."
},
"variants": [
{ "id": "v_dv360", "name": "DV360 (Control)", "description": "$60K, DV360 Optimized Targeting + floodlight conversion tracking", "is_control": true },
{ "id": "v_ttd", "name": "The Trade Desk", "description": "$60K, TTD AI bidding + UID2.0 cross-device reach", "is_control": false }
],
"results": {
"winner_variant_id": "v_dv360",
"stat_sig_achieved": true,
"confidence_level": 95,
"primary_metric": "prospecting_CPA",
"primary_metric_lift_pct": -11,
"conclusion": "DV360 11% lower CPA ($34.20 vs $38.50). TTD showed 22% better cross-device frequency control.",
"action_taken": "DV360 retained. TTD access maintained for re-evaluation in 2027."
}
}
]
}
}Field | Required | Notes |
| Yes | 90 or 95 are most common |
| Yes | Plain-English rule — Claude quotes this when analyzing tests |
| Yes | Claude uses this to evaluate whether the test was well-designed |
| Yes | See type values below |
| No | Negative = improvement for cost metrics (CPA, CPC) |
| No | Populate for dsp/agency/ad_network/platform/tool tests |
| No | List of KPIs or factors used to determine the winner |
| No | Final recommendation — quoted by Claude when reviewing past evaluations |
In-campaign test types: creative · audience · bidding · landing_page · copy · format · offer
Vendor/partner evaluation types: dsp · agency · ad_network · platform · tool
Catch-all: other
Test status values: planned · active · completed · paused · abandoned
Tip: Both categories are queried by the same list_tests and get_test_learnings tools. You can filter by type: "dsp" to pull only DSP evaluations, or leave the filter blank to see all tests and evaluations together. The more history you document — across both categories — the more Claude can surface institutional knowledge when you need it.
audiences.json
Documents your full audience infrastructure: first-party segments, contracted data providers, data onboarding platforms, lookalike strategy, and third-party overlay layers.
{
"audience_library": {
"business_unit": "Consumer",
"first_party_audiences": [
{
"id": "aud_purchasers_180d",
"name": "Past Purchasers — 180 Days",
"type": "pixel_based",
"description": "All purchasers in the last 180 days.",
"size_estimate": 45000,
"platforms_available": ["meta", "google_ads"],
"refresh_cadence": "daily",
"tags": ["purchasers", "lookalike-seed"]
}
],
"data_providers": [
{
"id": "dp_oracle",
"name": "Oracle Data Cloud",
"contract_status": "active",
"segments_available": ["In-Market: Home & Garden"],
"platforms_available": ["dv360", "google_ads"],
"contract_end_date": "2026-12-31"
}
],
"onboarding_platforms": [
{
"id": "ob_liveramp",
"name": "LiveRamp",
"type": "data_onboarding",
"platforms_connected": ["meta", "google_ads", "dv360"],
"use_cases": ["CRM list onboarding", "Identity resolution"]
}
],
"lookalike_strategy": {
"default_expansion_pct": 1,
"entries": [
{
"seed_audience_id": "aud_purchasers_180d",
"seed_audience_name": "Past Purchasers — 180 Days",
"platform": "meta",
"expansion_percentages": [1, 2, 5],
"best_performing_expansion": 1
}
]
},
"third_party_layers": [
{
"id": "3p_in_market",
"name": "In-Market: Your Category",
"provider": "Google",
"category": "in-market",
"platforms_available": ["google_ads", "dv360"],
"is_default": true,
"is_best_performer": true,
"cpm_premium_estimate": 0.50
}
]
}
}First-party audience types: crm_list · pixel_based · customer_match · email_list · app_users · lookalike_seed · suppression · other
Onboarding platform types: clean_room · data_onboarding · identity_resolution · cdp
Tip: Set is_default: true on audience layers you apply to every campaign, and is_best_performer: true on layers with proven efficiency gains — Claude uses both flags to make targeting recommendations.
measurement.json
Documents your full tracking and measurement infrastructure: tag management, pixels, Conversion APIs, CM360/Floodlight setup, website data layer, and measurement partners.
{
"measurement_setup": {
"tag_management": {
"system": "google_tag_manager",
"container_id": "GTM-XXXXXXX",
"implementation_type": "hybrid",
"server_side_endpoint": "https://sst.yourcompany.com"
},
"pixels_and_tags": [
{
"id": "pixel_meta",
"name": "Meta Pixel",
"platform": "meta",
"pixel_id": "XXXXXXXXXXXXXXXX",
"implementation": "both",
"events_tracked": ["PageView", "AddToCart", "Purchase"],
"custom_parameters": ["value", "currency", "order_id"]
}
],
"conversion_apis": [
{
"id": "capi_meta",
"platform": "meta",
"api_name": "Meta Conversions API (CAPI)",
"implementation": "both",
"events_sent": ["Purchase", "Lead"],
"match_rate_estimate_pct": 87,
"deduplication_method": "event_id matched client-side and server-side"
}
],
"cm360": {
"account_id": "XXXXXXX",
"floodlight_configuration_id": "FL-XXXXXXXX",
"u_variables": [
{ "variable": "u1", "name": "order_id", "type": "string", "description": "Unique order ID for deduplication" },
{ "variable": "u2", "name": "order_value", "type": "number", "description": "Order revenue in USD" }
]
},
"website_data_capture": {
"data_layer_implemented": true,
"data_layer_spec_url": "https://docs.yourcompany.com/datalayer",
"data_layer_variables": ["pageType", "transactionId", "transactionRevenue", "userId"],
"analytics_platform": "Google Analytics 4",
"analytics_property_id": "G-XXXXXXXXXX",
"first_party_cookies_implemented": true,
"cookie_domain": ".yourcompany.com",
"cookie_session_duration": "13 months",
"cookie_data_captured": ["_ga", "_gcl_aw (GCLID)", "_fbc (Meta click ID)"]
},
"measurement_partners": [
{
"id": "mp_mmm",
"name": "Your MMM Provider",
"type": "mmm",
"status": "active",
"platforms_covered": ["google_ads", "meta", "dv360"],
"cadence": "quarterly"
}
]
}
}Tag management system values: google_tag_manager · tealium · adobe_launch · segment · mparticle · manual · other
Implementation type values: client_side · server_side · hybrid
Measurement partner types: mmm · incrementality · brand_lift · attribution · analytics · identity · other
Tip: The cm360.u_variables section is especially useful — Claude can explain what each u-variable captures and help troubleshoot Floodlight discrepancies when you reference specific variable names.
platforms.json
Documents GMP bulk upload schemas for DV360 (SDF v7), SA360 (Bulksheet), and CM360 (Trafficking Sheet). Claude uses this to generate upload-ready files and to enforce your org's naming conventions and field defaults.
Each platform entry has an entity_types map (each entity type has a fields array) and an org_defaults object where you set your company's standard values.
{
"platforms_config": {
"platforms": {
"dv360": {
"name": "Display & Video 360",
"format": "SDF v7 CSV",
"entity_types": {
"line_item": {
"fields": [
{
"name": "Line Item Id",
"required": true,
"description": "Leave blank for new line items; SDF ID for existing",
"valid_values": "Blank (new) or existing SDF ID"
},
{
"name": "Line Item Type",
"required": true,
"description": "Line item buying type",
"valid_values": "LINE_ITEM_TYPE_DISPLAY_DEFAULT | LINE_ITEM_TYPE_VIDEO_DEFAULT | LINE_ITEM_TYPE_YOUTUBE_AND_PARTNERS_VIDEO_SEQUENCE"
}
]
}
},
"org_defaults": {
"notes": "SDF version: 7. Always download the existing SDF before uploading edits.",
"naming_conventions": {
"line_item": "{brand}_{tactic}_{audience}_{geo}_{quarter}_{year}"
},
"field_defaults": {
"Line Item - Status": "Draft",
"Line Item - Budget Type": "Amount"
}
}
},
"sa360": { },
"cm360": { }
}
}
}Entity types by platform:
Platform | Entity types |
|
|
|
|
|
|
Org defaults tip: Populate naming_conventions and field_defaults for every entity type your team regularly creates. Claude uses these to pre-fill the bulk upload files it generates, saving significant trafficking time.
What Claude can do
The server exposes 68 tools across 16 categories, 17 MCP resources, and 15 pre-built prompt templates.
Category | Tools |
Campaigns & accounts |
|
Teams |
|
Performance |
|
Attribution |
|
Reporting |
|
Assets |
|
Testing |
|
Audiences |
|
Measurement |
|
GMP bulk upload |
|
Analytics (BQ) |
|
Account analytics (BQ) |
|
Identity & signals (BQ) |
|
Data governance (BQ) |
|
Agent integration (BQ) |
|
Media actions (BQ) |
|
Tools marked (BQ) require BigQuery mode (PAID_MEDIA_GCP_PROJECT env var set;
the legacy name BIGQUERY_PROJECT_ID still works but logs a deprecation warning).
→ Full tool, resource, prompt reference, and example conversations: TOOLS.md
Data sources and adapters
Why JSON by default
JSON files are the default not because they're the most powerful option, but because they're the most universally accessible one.
In most enterprise environments — agencies, in-house media teams, large corporations — direct API access to ad platforms is restricted. Developer tokens require approval. OAuth credentials need IT involvement. Production API quotas are tightly controlled. A team that wants to give Claude context about their paid media program shouldn't have to file an IT ticket to do it.
JSON files sidestep all of that. Any media planner can export a campaign list to CSV, convert it to JSON, and drop it in the data/ folder in five minutes. No credentials, no SDK, no API rate limits, no approval workflow.
There's also a more fundamental reason: most of what makes this MCP valuable isn't in any API. Your team structure, attribution rules, audience strategy, testing history, agency evaluations, naming conventions, reporting templates, measurement setup — none of this exists in a platform API. It lives in spreadsheets, Confluence pages, people's heads, and Slack messages. JSON is the right format for capturing that institutional knowledge regardless of where your campaign data comes from.
The result is a two-layer model that most teams will use:
Org knowledge (teams, attribution, audiences, testing, measurement) → JSON files, updated periodically
Platform data (campaigns, performance) → JSON exports, a BQ table, or a live API
When to use which source
Your situation | Recommended approach |
Getting started, or restricted API access | JSON files — export from platform UI, drop in |
Platform data exported to BigQuery or a data warehouse | BigQueryAdapter — queries your existing tables |
Need real-time campaign data from a specific platform | Custom API adapter — implement |
Data is scattered across BQ, APIs, and JSON | CompositeAdapter — route each domain to its best source |
These options aren't mutually exclusive. Most production setups end up using the CompositeAdapter to combine them.
Option A — Local JSON files (default)
No configuration needed. Edit the files in data/ and restart the server.
Best for:
Any environment where API access is unavailable or restricted
Organizational knowledge that doesn't live in a platform (teams, attribution, testing, measurement)
Getting up and running quickly
// src/index.ts — already the default
const adapter = new FileAdapter(DATA_DIR);Option B — BigQuery or data warehouse
Use BigQueryAdapter when your campaign data and performance metrics are exported to BigQuery (via Fivetran, Supermetrics, Stitch, dbt, or your own pipeline). It queries your BQ tables for campaigns and performance while falling back to JSON files for organizational knowledge.
Install the BigQuery client:
npm install @google-cloud/bigquerySet up authentication:
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json"Or on GCP infrastructure, attach a service account with BigQuery Data Viewer access.
Environment variables (canonical names, shared with paid-media-agent):
export PAID_MEDIA_GCP_PROJECT="your-gcp-project" # enables BigQuery mode
export PAID_MEDIA_BQ_DATASET="paid_media" # default: paid_media
export PAID_MEDIA_AGENT_URL="https://your-cloud-run-url" # optional — agent integrationCredential scoping (Vercel / non-GCP hosts): the service account behind
GOOGLE_APPLICATION_CREDENTIALS_JSON is a full key sitting in a third-party
host's env store — scope it to the minimum: grant roles/bigquery.dataViewer
on the one dataset (not project-wide) plus roles/bigquery.jobUser on the
project. The MCP only reads; it never needs dataEditor. Rotate the key
periodically, and never log or echo the credential object. Longer term,
prefer Workload Identity Federation over a static key.
# Dataset-scoped read access (replace placeholders)
bq update --dataset \
--source <(bq show --format=prettyjson YOUR_PROJECT:paid_media \
| jq '.access += [{"role":"READER","userByEmail":"mcp-reader@YOUR_PROJECT.iam.gserviceaccount.com"}]') \
YOUR_PROJECT:paid_media
gcloud projects add-iam-policy-binding YOUR_PROJECT \
--member="serviceAccount:mcp-reader@YOUR_PROJECT.iam.gserviceaccount.com" \
--role="roles/bigquery.jobUser"Remote (Vercel) auth: the /api/mcp endpoint requires
Authorization: Bearer <MCP_API_KEY>. Set MCP_API_KEY in the Vercel
dashboard (generate with openssl rand -hex 32) and add the matching
headers entry in your Claude Code settings.json (see api/mcp.ts header
for the full example). Requests without the key get 401; the server refuses
to start unauthenticated unless MCP_ALLOW_UNAUTHENTICATED=true is set
explicitly.
The legacy names BIGQUERY_PROJECT_ID / BIGQUERY_DATASET_ID still work as
fallbacks (with a deprecation warning); setting old and new names to
conflicting values fails at startup. All env resolution lives in
src/config.ts.
External staging tables (prerequisites for some tools):
detect_crm_null_fields, query_account_journey's revenue layer, and the
reporting-view tools (get_campaign_downstream_roi) read three tables the
agents do not create or populate:
Table | Populated by | Used for |
| Your GA4 BigQuery export ETL | Traffic-layer joins ( |
| Your CRM (Salesforce/HubSpot) export | Lead → web linkage, null-field audits |
| Your CRM export | Pipeline stage, deal |
The paid-media-agent schema ships stub DDL for all three
(schema/bigquery/02_touchpoints.sql and 18_external_staging.sql, which
documents the full column contract). Until they're loaded, these tools return
empty results.
Expected table schemas:
The BigQueryAdapter targets the paid-media-agent canonical schema. If using that deployment, the tables already exist. If connecting your own warehouse, match this structure or edit the queries in bigquery-adapter.ts.
The full cross-repo column contract (every agent-output table this server reads, the HTTP routes it calls, and the
task27.v1package format) lives inCONTRACT.mdin paid-media-agent. Changes to those interfaces must update that file in the same PR.
-- platform_campaigns
CREATE TABLE platform_campaigns (
campaign_id STRING, campaign_name STRING, platform STRING,
account_id STRING, team_id STRING, status STRING,
objective STRING, funnel_stage STRING,
budget_amount NUMERIC, budget_type STRING, budget_currency STRING,
start_date DATE, end_date DATE, notes STRING,
tags STRING -- comma-separated list
);
-- platform_daily_spend
CREATE TABLE platform_daily_spend (
date DATE, campaign_id STRING, platform STRING,
impressions INT64, clicks INT64, spend NUMERIC,
conversions INT64, conversion_value NUMERIC
);
-- benchmarks (optional)
CREATE TABLE benchmarks (
platform STRING, objective STRING,
avg_ctr FLOAT64, avg_cpc FLOAT64, avg_cpm FLOAT64,
avg_cpa FLOAT64, avg_roas FLOAT64
);Note: Financial columns use
NUMERIC(notFLOAT64) throughout the canonical schema to prevent floating-point rounding on currency values.
Wire it up in src/index.ts:
import { BigQueryAdapter } from "./adapters/bigquery-adapter.js";
const adapter = new BigQueryAdapter({
projectId: process.env.BQ_PROJECT_ID!,
dataset: process.env.BQ_DATASET!,
// Optional: override table names if yours differ
tables: {
campaigns: "my_campaigns_table",
performance: "my_daily_stats_table",
},
// Optional: path to JSON files for org knowledge (default: "./data")
dataDir: DATA_DIR,
});The BigQueryAdapter overrides getCampaigns, getCampaign, getPerformance, and getBenchmarks with BQ queries. Everything else (teams, attribution, audiences, testing, measurement) is inherited from FileAdapter and reads from your JSON files.
If your column names differ from the schema above, edit src/adapters/bigquery-adapter.ts — the queries are straightforward SQL near the bottom of the file.
Other data warehouses: The BigQueryAdapter pattern applies equally to Snowflake, Redshift, and other warehouses. Create a new adapter that extends FileAdapter and overrides the same methods with your warehouse's SDK. The interface contract is identical.
Option C — Live platform API
For real-time data from a specific ad platform, implement the PaidMediaAdapter interface directly or extend FileAdapter for the domains you want to override.
// src/adapters/google-ads-adapter.ts
import { FileAdapter } from "./file-adapter.js";
import type { CampaignFilters } from "./base.js";
import type { Campaign } from "../types.js";
export class GoogleAdsAdapter extends FileAdapter {
constructor(
private customerId: string,
private developerToken: string,
dataDir = "./data"
) {
super(dataDir);
}
override async getCampaigns(filters: CampaignFilters = {}): Promise<Campaign[]> {
// Call Google Ads API here
// Return Campaign[] matching the interface
}
// Override getCampaign, getPerformance, etc. as needed
// Everything else falls through to FileAdapter (JSON)
}Swap it in src/index.ts:
import { GoogleAdsAdapter } from "./adapters/google-ads-adapter.js";
const adapter = new GoogleAdsAdapter(
process.env.GOOGLE_ADS_CUSTOMER_ID!,
process.env.GOOGLE_ADS_DEVELOPER_TOKEN!
);Option D — Multiple sources combined
CompositeAdapter routes each data domain to a different adapter. Use this when your data genuinely lives in multiple places — campaign metadata in BQ, audiences pulled live from Meta's API, and org knowledge in JSON files.
import { FileAdapter } from "./adapters/file-adapter.js";
import { BigQueryAdapter } from "./adapters/bigquery-adapter.js";
import { CompositeAdapter } from "./adapters/composite-adapter.js";
const files = new FileAdapter(DATA_DIR);
const bq = new BigQueryAdapter({
projectId: process.env.BQ_PROJECT_ID!,
dataset: process.env.BQ_DATASET!,
});
const adapter = new CompositeAdapter({
default: files, // JSON for everything not explicitly overridden
campaigns: bq, // BQ for campaign metadata
performance: bq, // BQ for daily performance records
});Every domain can be routed independently:
Domain key | Methods routed |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Common real-world configuration: campaigns and performance from BQ (daily platform exports), audiences from a live API (so segment membership is always current), everything else from JSON.
const adapter = new CompositeAdapter({
default: new FileAdapter(DATA_DIR),
campaigns: new BigQueryAdapter({ projectId: "...", dataset: "..." }),
performance: new BigQueryAdapter({ projectId: "...", dataset: "..." }),
audiences: new MetaAudienceAdapter({ accessToken: process.env.META_TOKEN! }),
});All tools, resources, and prompts work without any changes — they only call the adapter interface, and never know which underlying source fulfilled each request.
Troubleshooting
Claude Desktop doesn't show the paid-media tools
Check the config path:
~/Library/Application Support/Claude/claude_desktop_config.json(Mac) or%APPDATA%\Claude\claude_desktop_config.json(Windows)Make sure paths in the config are absolute, not relative
Check that
npm run buildran without errors (dist/index.jsmust exist)Fully quit and reopen Claude Desktop (Cmd+Q on Mac, not just closing the window)
Check Claude Desktop's MCP logs: Settings → Developer → Logs
Server starts but returns no data
The server logs warnings (to stderr) for each data file it can't load. Run the server directly to see them:
node dist/index.jsYou'll see lines like:
[FileAdapter] Warning: Data file not found: /path/to/data/campaigns.jsonData changes aren't reflected
The server loads data files at startup. After editing any file in data/, restart the MCP server:
In Claude Desktop: Settings → Developer → restart the paid-media server
Or fully restart Claude Desktop
JSON syntax errors
If a data file has a syntax error, the server silently skips it. Validate your JSON at jsonlint.com or run:
node -e "JSON.parse(require('fs').readFileSync('data/campaigns.json', 'utf8'))"Performance data not aggregating correctly
Rate metrics (CTR, CPC, ROAS) are re-computed from raw totals during aggregation. If you include both raw counts and pre-calculated rates in your records, the server will sum both, which will double-count rates. Best practice: include only raw count metrics in records (impressions, clicks, spend, conversions, conversion_value) and let the server compute rates.
Project structure
paid-media-mcp/
├── src/
│ ├── index.ts # MCP server entry point
│ ├── types.ts # TypeScript interfaces for all data models
│ ├── adapters/
│ │ ├── base.ts # PaidMediaAdapter interface — implement to connect any source
│ │ ├── file-adapter.ts # Default: reads from data/*.json at startup
│ │ ├── bigquery-adapter.ts # BQ: campaigns + performance from BigQuery, org knowledge from JSON
│ │ └── composite-adapter.ts # Multi-source: route each domain to a different adapter
│ ├── tools/
│ │ ├── campaigns.ts # list_campaigns, get_campaign, list_accounts, get_account
│ │ ├── teams.ts # list_teams, get_team, get_team_for_account, list_team_members, get_team_member
│ │ ├── performance.ts # get_campaign_performance, get_team_performance, get_benchmarks
│ │ ├── attribution.ts # list_attribution_models, get_attribution_model, compare_attribution_models
│ │ ├── reporting.ts # list_reporting_templates, get_reporting_template, build_performance_report
│ │ ├── assets.ts # get_asset_library, list_asset_categories, get_asset_category, get_asset_specs
│ │ ├── testing.ts # get_testing_methodology, list_tests, get_test, get_test_learnings
│ │ ├── audiences.ts # get_audience_library_overview, list_first_party_audiences, list_data_providers,
│ │ │ # get_lookalike_strategy, list_third_party_audience_layers, get_onboarding_platforms
│ │ └── measurement.ts # get_measurement_overview, get_tag_management, list_pixels_and_tags,
│ │ # list_conversion_apis, get_cm360_setup, get_website_data_capture, list_measurement_partners
│ ├── resources/
│ │ └── index.ts # 17 MCP resources (org knowledge, schema contracts, live agent data)
│ └── prompts/
│ └── index.ts # 9 pre-built prompt templates
├── data/
│ ├── metadata.json # Company info
│ ├── accounts.json # Ad accounts by platform
│ ├── teams.json # Media teams, objectives, KPIs
│ ├── team-members.json # Team members, roles, responsibilities
│ ├── campaigns.json # All campaigns
│ ├── historical-performance.json # Daily metrics + benchmarks
│ ├── attribution-models.json # Attribution configurations
│ ├── reporting-templates.json # Report structures by audience
│ ├── assets.json # DAM info, asset categories, per-platform specs
│ ├── testing.json # Testing methodology, tools, and test history
│ ├── audiences.json # 1P audiences, data providers, LAL strategy, 3P layers
│ ├── measurement.json # TMS, pixels, Conversion APIs, CM360, data layer, partners
│ └── platforms.json # DV360 SDF v7, SA360 Bulksheet, CM360 schemas + org defaults
├── claude_desktop_config.example.json # Config snippet for Claude Desktop
├── package.json
├── tsconfig.json
├── README.md # Setup and data configuration guide
└── TOOLS.md # Full tool, resource, and prompt referenceContributing
Issues and pull requests welcome. If you build an adapter for a specific platform (Google Ads API, Meta Marketing API, Snowflake, Redshift, etc.), consider contributing it back.
License
Business Source License 1.1 (BSL 1.1). Persistent attribution required. See LICENSE and NOTICE for terms. © 2026 @arcticgreyy
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/kenlim5656/paid-media-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server