affset MCP server

MCP serverCommunication

This app connects your AI to affset, so you can handle your advertising work right from a chat. Once it is added, you can ask for stats, check payouts, adjust targeting, and manage campaigns without switching tools.

Available today. Use it from your connected AI after setup.

After adding it, open a chat and ask for your stats, campaigns, or payouts to see it working. From there you can run your day-to-day affset tasks in conversation.

Then ask your AI: use affset MCP server

What your AI can do with it

  • Pull campaign statistics on demand
  • Manage your campaigns from chat
  • Look up and work with zones
  • Check payouts
  • Adjust targeting settings
  • See who is on your team

From the project's README

As published by affset/mcp in README.md.

An MCP server that lets a media buyer run affset from a chat client — pull stats, manage campaigns/zones/team, payouts, targeting, sub labels, and cut underperforming zones in plain language, no dashboard.

The tools wrap the existing affset tenant API. Connect through the hosted endpoint (OAuth, no API key) or run this package locally (Bearer token + X-Namespace). One connection serves one tenant.

Fastest way to connect — the hosted endpoint. Add https://mcp.affset.com/mcp as a remote MCP server in Claude (web or desktop), Cursor, Claude Code, or any client that supports streamable HTTP with OAuth: paste the URL, sign in with your affset email, keep Read-only (the consent default) or grant full access. Every connection shows up on the dashboard Integrations page and can be revoked individually. Setup guide: affset.com/integrations.

The npm package below is the self-host path: same tool roster, runs on your machine with an API key you manage yourself. Stdio defaults to full access unless you set AFFSET_READ_ONLY=true.

Tools

ToolWhat it does
whoamiShow the tenant this server is bound to: namespace, API base, derived dashboard URL, and (when readable) company / timezone / custom API domain. Read-only.
get_statsTraffic stats grouped by a dimension (date, campaign, zone, country, sub1–5, advertiser, publisher, …), optionally narrowed with advertiser_email/publisher_email filters (one user, any group_by). Returns clicks, conversions, CR, payout, media cost and ROI as a table. paid_only defaults to true (same as the dashboard) so CR excludes informative conversions. Sub columns use the tenant's sub labels when configured. Per-user groupings and filters are owner/manager plus the matching side's manager role.
list_campaignsList campaigns (status / name filter, pagination).
get_campaignOne campaign's full record — every field (untruncated offer URL, exact schedule, budgets/pacing, silent flag, payout goal type) plus its targeting rules and payout rules, in one call.
list_zonesList traffic-source zones (status / name filter, pagination, linked source).
list_teamList team members (email, role, manager). Never returns API tokens.
create_team_memberInvite a team member (owner, manager, publisher, advertiser, publisher_manager, advertiser_manager). A scoped manager key can only create its own managed role, self-assigned. Returns the new API key oncelist_team never shows it again. Dry-run by default; confirm: true to apply.
create_campaignCreate a campaign from an advertiser email, offer URL, geo, payout and name. Defaults: CPA / rate 0, paused, global payout rule, and a ready tracking link (linked source template when configured; otherwise source_click_id={clickid} + sub placeholders). Dry-run by default; confirm: true to apply.
set_campaign_statusRun or pause a campaign (action: "run" | "pause"). Dry-run by default; confirm: true to apply. Running can hit the plan's active-campaign limit.
update_campaignPartial update (name, offer URL, status, rate, budgets, dates, …). Dry-run by default; confirm: true to apply. Prefer set_campaign_status for run/pause.
create_zoneCreate a traffic-source zone (name + optional postback/site/traffic-back URLs, optional traffic_source_id link). Always created active. Dry-run by default; confirm: true to apply.
update_zonePartial update (name, status, URLs, traffic_source_id). Dry-run by default; confirm: true to apply. Pass null to clear a URL or unlink the source.
list_traffic_sourcesList traffic sources — the networks bought from, each with the tracking/postback templates its linked zones use. API token shown only as set/none.
create_traffic_sourceCreate a traffic source, optionally from a network preset (exoclick, trafficstars, propellerads, adsterra, richads) that copies verified templates into an editable row. Dry-run by default; confirm: true to apply.
update_traffic_sourcePartial update (name, templates, api_token, status). Linked zones pick the new tracking template up immediately. Dry-run by default; confirm: true to apply.
get_zone_urlThe /serve URL to paste into a network's campaign settings — rotates across the zone's active campaigns. A zone linked to a traffic source renders that source's tracking template; otherwise prefilled sub convention + optional cost macro. Warns when no active campaigns are visible.
get_tracking_linkThe /track/click link for an existing campaign + zone — straight to one active campaign, with no rotation or targeting checks. Renders a linked source's template like get_zone_url. Re-derives what create_campaign echoed on create.
cut_zonesBlacklist underperforming zones on a campaign by threshold (CR / spend / ROI). Dry-run by default; confirm: true to apply.
list_payout_rulesList a campaign's global + per-zone payout rules and its payout_goal_type.
set_payout_ruleUpsert a global or zone-specific payout. Dry-run by default; confirm: true to apply.
delete_payout_ruleDelete a global or zone-specific payout rule. Dry-run by default; confirm: true to apply.
set_payout_goalSet or clear payout_goal_type (goal-based conversions). Dry-run by default; confirm: true to apply.
list_targeting_typesCatalog of targeting rule types, flagging the seeded ones /serve never evaluates.
list_targeting_rulesList a campaign's targeting rules, flagging any that have no effect.
set_targeting_ruleUpsert one targeting rule (safe merge), normalised to what /serve matches. Dry-run by default; confirm: true to apply.
remove_targeting_ruleRemove one targeting rule by id or type+method. Dry-run by default; confirm: true to apply.
list_sub_labelsList tenant display names for sub1–sub5.
set_sub_labelsSet or clear sub labels (partial; null clears). Dry-run by default; confirm: true to apply.
list_conversionsList conversion audit records (payout, spend, pixel type, payload, postback). paid_only filters server-side; other optional filters are client-side on the current page.

Which URL do I give the network?

get_zone_url (/serve/{zone})get_tracking_link (/track/click/{campaign}/{zone})
Picks the campaignaffset, from the zone's rotationyou, one fixed campaign
Needs an active campaignyes — otherwise traffic back / unsoldyes — otherwise 404
Needs an active zoneyesyes
Geo & targeting rulesenforcednot enforced
cost= lands onthe impression rowthe click row

Use one or the other for a given traffic stream — never both with cost=, or the media cost is counted twice.

Both use the tenant's custom API domain when one is set, since the URL gets pasted into the network verbatim. Macros ({clickid}, [CLICK_ID], ${SUBID}) are inserted without percent-encoding — the source expands them before the request reaches affset.

cut_zones only ever adds zones to a campaign's blacklist, and does a read-merge-write so existing targeting rules are never touched.

create_campaign needs a traffic-source zone for the tracking link: pass zone_id, or let it auto-pick when the namespace has exactly one active zone. Campaigns are created paused; activate them before sending traffic through either URL. Both URL types also require an active zone. Geo whitelist is enforced in /serve only — the direct tracking link is not geo-gated, but it still requires an active, currently serviceable campaign.

Documentation resources

Beyond the tools, the server exposes the affset API reference as MCP resources, so an assistant can answer "how does conversion tracking work?" or "what does /serve accept?" from the docs themselves — not just from the tool schemas.

Resource URITypeContent
affset://docs/api-referencetext/markdownThe full API reference — endpoints, auth, roles, examples.
affset://docs/api-reference.jsonapplication/jsonThe same reference as structured data, for programmatic use.

They're the exact content published at affset.com/docs, generated from one source, and fetched at read time from AFFSET_DOCS_URL ({origin}/api-reference.md and {origin}/api-reference.json) — so they always reflect the currently published docs, not a copy pinned to this package. The fetch sends no credentials (the docs are public and live on a different origin than the tenant API). HTML SPA fallbacks, redirects, invalid JSON, and oversized bodies are rejected. Both resources are always available, including under AFFSET_READ_ONLY.

Configuration

Self-host (stdio) only — hosted connections do not use these variables. All config comes from the environment (never hard-coded):

Shortened here. Read the whole README on GitHub.

Advanced
Delivery
mcp MCP server → your ahel gateway (mcp.ahel.ai) → every connected AI client.
Catalog kind
mcp-server
Gateway key
io-github-affset-mcp
Source
github.com/affset/mcp
Hosted endpoint
https://mcp.affset.com/mcp