servicehawk-ad-intel
# ServiceHawk Ad Intel MCP
An MCP server for building a competitive ad swipe file from public Meta Ads
Library research, for ServiceHawk, an agency that sells automation to home
service businesses (plumbers, HVAC, pest control, roofers, electricians).
It is not a bot for logging into Meta, scraping past a paywall, or bypassing
rate limits. It does none of that, on purpose:
- No credentials, ever. It has no login flow and cannot be given one.
- No fetching outside a small host allowlist (facebook.com,
www.facebook.com, m.facebook.com, transparency.meta.com by default).
- No destructive tools. Every write goes to a local append-only file.
- No cloning. The scoring tools grade a pattern's reusable structure
(hook, offer, proof, format); the copy generator writes new lines from
that structure, never a competitor's actual sentence.
What it is: a small, honest tool for turning public ad research into a
scored, searchable swipe file, plus a generator that drafts new ServiceHawk
ad copy from the angles that keep showing up as winners.
## Why this exists
Meta's official Ad Library API only really covers social issue, election,
and politics ads worldwide, plus all ad types for the UK and EU over the
past year. For ordinary US commercial competitor research, that leaves the
public Ad Library web UI and manually saved pages, not a clean API you can
query in bulk. This server is built around that constraint instead of
pretending it does not exist: it helps you build the right search URLs,
captures a public page when one is reachable without login, and gives you a
structured way to record what you saw by hand when it is not.
## Install
```bash
cd servicehawk-ad-intel-mcp
npm install
npm run smoke
npm run smoke:mcp
```
`npm run smoke` exercises the scoring and storage logic directly.
`npm run smoke:mcp` spins up the server over stdio and calls it as a real
MCP client would, so both checks run against the actual code path, not a
mock.
## Run
```bash
node /path/to/servicehawk-ad-intel-mcp/src/server.js
```
It speaks MCP over stdio, so in practice you point an MCP client at it
instead of running it by hand. Example client config:
```json
{
"mcpServers": {
"servicehawk-ad-intel": {
"command": "node",
"args": ["/path/to/servicehawk-ad-intel-mcp/src/server.js"]
}
}
}
```
Any MCP-capable client (Claude Code, Claude Desktop, or your own MCP client
built on the SDK) can load it this way. The server has one job: expose the
tools below over stdio. It does not manage its own client connection or UI.
## Tools
| Tool | Takes | Returns |
|---|---|---|
| `build_meta_ad_library_url` | Optional `query`, `page_id`, `country` (default `US`), `media_type`, `ad_type`, `active_status` (default `active`) | `{ url, note }`, a public `facebook.com/ads/library` search URL to open in a browser |
| `capture_public_ad_page` | `url` (required, must match the host allowlist), optional `timeout_ms`, `max_text_chars` | `{ ok, capture, path }` with the page title, meta description, stripped visible text, and a `blocked_likely` flag; or `{ ok: false, error }` if the host is not allowlisted or the fetch fails |
| `record_ad_swipe` | `page_name`, `headline`, `visual_notes`, `offer` (required), plus optional `source_url`, `vertical`, `format`, `primary_text`, `description`, `landing_page_url`, `active_since`, `active_days`, `variant_count`, `seen_repeated`, `notes` | `{ ok, record }`, the stored swipe with a computed `score` block, appended to `data/swipes.jsonl` |
| `score_ad_pattern` | Same ad fields as above, without saving | `{ score, verdict, factors, inferred_strategy, caveat }`, scored but never written to disk |
| `list_ad_swipes` | Optional `vertical`, `offer` substring filters, `min_score`, `limit` | `{ ok, count, records }`, stored swipes sorted by score |
| `summarize_winning_patterns` | Optional `min_score`, `top` | `{ ok, total_swipes, top_strategies, top_swipes, servicehawk_recommendation, next_research_moves }` |
| `generate_servicehawk_variants` | `angle` (`review_gap` or `finished_job`), optional `cta` | `{ ok, angle, variants, note }`, drafted ad copy for that angle family, explicitly marked as a starting point to validate before launch |
`score_ad_pattern` rewards signals that predict a reusable pattern: how long
an ad has run, how many similar variants exist, whether the copy lands on a
home-service pain point, whether the offer is a low-friction diagnostic
(free audit, free report), and whether the headline is short enough to read
in a feed scroll. It penalizes risky claims (guarantees, "#1", platform
trademarks in the copy) and bloated primary text. The score is a signal for
prioritizing research, not a prediction of CPA or ROAS, and the tool says so
in its own output.
## Data
Everything is stored locally as flat files, not a database:
```text
data/swipes.jsonl one recorded ad observation per line
data/captures/*.json saved public-page captures
```
Override the location with `AD_INTEL_DATA_DIR`. See `.env.example`.
## Safety
- No credentials anywhere in this server or its config.
- No logged-in scraping and no bypassing of Meta's own blocks. If a page
comes back blocked, the tool says so and tells you to capture it by hand.
- No arbitrary URL fetching. `capture_public_ad_page` checks the hostname
against an allowlist before it makes a request, full stop.
- Every write is append-only to a local file. Nothing here deletes or
overwrites past research.
## Approach
This server ships with a written contract for every tool: what it takes,
what it returns, and what it explicitly refuses to do (see Safety, above).
Getting that contract right did not come from one pass at the code.
The work was split across roles that each owned one narrow piece of it
(the tool contracts, the scoring rules, the safety constraints) instead of
one generalist trying to hold the whole thing in its head at once. A
separate, independent pass then checked the result against the actual
source code rather than trusting the first answer, and a set of
deterministic checks, not a model's own say-so, decided what was safe to
publish. That is the same standard applied to every claim in this README:
the tool list, the input contracts, and the safety behavior above were
verified against `src/lib.js` and `src/server.js`, not written from memory.
## Layout
| Path | What |
|---|---|
| `src/server.js` | MCP server: registers the tools above and wires each one to `lib.js` |
| `src/lib.js` | The actual logic: URL building, allowlisted fetch, scoring, and swipe file storage |
| `scripts/smoke-test.js` | Exercises the scoring and storage logic directly, no MCP layer |
| `scripts/mcp-client-smoke.js` | Spawns the server and calls it as a real MCP client, over stdio |
## Limits
- The scoring model is a fixed set of hand-written rules, not a trained
model. It rewards home-service and diagnostic-offer signals specifically,
not general ad quality, so a strong score in a different vertical means
less.
- Meta can and does block automated fetches of Ad Library pages. When that
happens the right move is manual capture into `record_ad_swipe`, not a
workaround, and this server does not try to build one.
- `capture_public_ad_page` only extracts text; it does not download images
or video, and it does not render JavaScript.
## About
Built by Roderick Finch, ServiceHawk AI (servicehawkai.com), Pembroke
Pines, FL.
MIT licensed.
TDQS
Scored across 7 tools
Each tool targets a distinct step in the ad research workflow: generating variants, building search URLs, capturing pages, scoring/recording patterns, listing, and summarizing. The only mild overlap is between generate_servicehawk_variants and summarize_winning_patterns, but their inputs and outputs are clearly different enough.
All tool names follow a predictable snake_case verb_noun pattern: generate_, build_, capture_, record_, score_, list_, summarize_. This makes the tool set easy to navigate and mentally model.
Seven tools is well-scoped for an ad intelligence server, covering generation, capture, evaluation, storage, retrieval, and synthesis without unnecessary duplication. Each tool earns its place in the workflow.
The core ad-research lifecycle is covered: capture, score, record, list, and summarize. The main gap is the lack of update/delete operations for stored swipes, but agents can still complete the primary workflow without them.