Most ad-research tools make you leave your work to use them. You're drafting a campaign in Claude, you want to know what your competitor's longest-running ad looks like, and now you're tab-switching into a dashboard, copy-pasting a URL, waiting for a scrape, and pasting a screenshot back. The Model Context Protocol kills that round-trip. We built an MCP server so AdWhispr's ad intelligence lives inside the place you're already thinking — and shipping it taught us a lot about what makes an MCP server actually good versus merely functional.
This is the engineering write-up. If you're MCP-curious — and if you run paid media, you probably are — here's what we learned designing eight tools, wiring OAuth, deciding read-only versus write, and returning images instead of walls of JSON.
What MCP actually is (and why it's a distribution channel)
MCP is an open protocol that lets an AI client — Claude, ChatGPT, Cursor, and a growing list of others — call your server's tools mid-conversation. You expose a set of typed tools; the client decides when to call them based on the user's intent; your server runs the work and returns a result the model can reason over.
The framing that matters for product people: MCP is a distribution channel, not just an API. A traditional API sits behind your own UI that you have to convince people to visit. An MCP server installs into a surface where millions of users already spend hours a day. You don't fight for the tab — you become a verb inside their existing chat. For us that means a media buyer in the middle of strategy work can say "what's Hims running right now that's been live 90+ days?" and the answer renders in the same thread, grounded in real Meta Ad Library data, no context-switch.
That changes how you build. Your "UI" is now the model's tool-call planner. Tool names, descriptions, and argument schemas are your product surface.
Designing the tools: the AdWhispr eight
We ship eight tools. The split between them is the whole design:
| Tool | Type | What it returns |
|---|---|---|
search_brands |
read | Brands we've indexed matching a query |
add_brand |
write-ish | Kicks off ingesting a new Facebook page's ad library |
get_brand_ads |
read | A brand's ads, filterable by status/format |
get_brand_stats |
read | Derived stats: longevity distribution, iteration rate |
search_ads |
read | Semantic search across ad creative and copy |
compare_brands |
read | Side-by-side of two or more brands' strategies |
clone_ad |
write | A new original creative grounded in a real winner |
generate_brief |
read | A competitive brief (PDF + Markdown) |
The first lesson: name tools for user intent, not your internal schema. get_brand_stats could have been query_aggregates — but the model has to pick it from a verbal request like "how often does CalAI ship new creative?" The closer the name and description sit to how a human phrases the goal, the more reliably the client routes to the right tool. We rewrote every description twice after watching the model pick wrong.
Second lesson: keep arguments shallow and typed. Tools with five nested-object parameters get called wrong. get_brand_ads takes a brand identifier, an optional status filter, and a limit — that's it. If you need richer behavior, add a second tool rather than overloading one with a mode flag.
Third lesson: return text the model can talk over, not raw dumps. Early on get_brand_stats returned the literal aggregate JSON. The model would faithfully recite it. Now it returns a compact, labeled summary — "23 ads live 100+ days; longevity skews long, suggesting proven winners; ~6 new creatives/month" — and the model can reason and caveat from there. The honest framing is baked in at the tool layer: we never emit a competitor's CTR, CPC, or ROAS, because Meta's Ad Library doesn't expose them and inventing them would be the easiest way to make our own tool lie. Spend and impressions go out only as ranges, with the inputs behind any derived signal cited in the payload.
Read-only versus write tools, and annotations
Six of our eight tools are read-only. Two — add_brand and clone_ad — change state or cost money/credits. MCP lets you declare this with tool annotations: hints like readOnlyHint and destructiveHint that tell the client a tool is safe to call freely versus one that should ideally get a beat of user intent first.
This matters more than it sounds. A read-only tool can be called speculatively — the model can fire get_brand_stats and search_ads to gather context without consequence. A write tool shouldn't be. clone_ad consumes a monthly clone allotment; add_brand spins up a full ingestion job. We annotate them accordingly so clients don't trigger billable work as a side effect of "just checking." We also keep our entire product read-only on competitor data — AdWhispr never touches anyone's live ad account and never launches a campaign. The only "writes" are to our own index (add_brand) and to your generated assets (clone_ad). That boundary is a feature, and the annotations make it legible to the client.
Returning text versus inline images
The most fun engineering problem was clone_ad. It's grounded in a real verified winner — usually one of a brand's longest-running ads — and produces something original in your brand identity. For an image ad it generates a new image; for a video ad it returns a scene-by-scene script brief, shot list, and UGC creator brief as text.
MCP tool results aren't limited to text — a tool can return image content blocks, and a capable client will render them inline. So clone_ad on an image winner streams back the generated creative in the conversation, next to the source it was grounded in, with a citation. No "open this link to view." The user sees the competitor's proven format and their own original riff side by side, in the chat, in one turn.
The lesson there: design tool outputs for the rendering surface, not just for the model. Text tools should return prose the model narrates. Visual tools should return image blocks. Mixing them — image plus a short text caption explaining what was cloned and from where — is what makes the result feel native rather than like an API response someone pasted in.
Auth: OAuth and Bearer, two paths in
You can connect to our server two ways. The hosted path is OAuth against https://adwhispr.com/api/mcp — you authorize once inside Claude.ai's connector flow, the client stores the grant, and every subsequent tool call carries it. The local path is npx adwhispr-mcp-server config, which runs the server on your machine with a Bearer token you paste in.
Two hard-won notes. First, OAuth discovery metadata must be served as static content from your CDN, not generated by a function per request — clients fetch it during the handshake and a cold serverless function in that path causes intermittent, maddening connection failures. Second, every tool handler must resolve the caller's identity from the token and scope every query to their account and plan. Plan limits — free is 5 messages and 1 brand; Pro is unlimited tool calls with 3 brands and 10 clones a month; Agency goes to 10+ brands and 50 clones — are enforced server-side per request, never trusted from the client. The token tells you who's asking; the server decides what they're allowed.
Lessons that generalize
A few things we'd tell anyone building a server in this space:
- The description is the UX. You're writing for a model's tool-selection step. Be concrete about when to use each tool.
- Fewer, sharper tools beat many overlapping ones. Eight that each own a clear job route better than fifteen with fuzzy edges.
- Annotate writes, default to read-only. It makes you safe to use speculatively and keeps billable actions deliberate.
- Match output type to the client's renderer — text for narration, image blocks for creative.
- Bake your honesty into the payload. If your domain has metrics that can't be known (competitor CTR/ROAS), don't let any tool emit them. Cite inputs instead.
MCP turned AdWhispr from a dashboard you visit into intelligence that shows up where the work happens. For a research tool, that's the whole game — see more posts on the blog, or connect the server and try it at adwhispr.com.
Stop tab-switching to spy on competitors — bring their ad library into your chat.