@buygolinks/mcp 0.9.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -5
- package/package.json +1 -1
- package/src/index.js +323 -16
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @buygolinks/mcp
|
|
2
2
|
|
|
3
|
-
Model Context Protocol (MCP) server for [buygolinks.com](https://buygolinks.com) — create and manage branded short links
|
|
3
|
+
Model Context Protocol (MCP) server for [buygolinks.com](https://buygolinks.com) — create and manage branded short links, campaigns, offers, and analytics from any MCP-compatible AI agent: Claude Desktop, Cursor, Cline, Continue, Hermes, and others.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -62,8 +62,8 @@ Most MCP clients use the same shape. Point them at the `npx -y @buygolinks/mcp`
|
|
|
62
62
|
|
|
63
63
|
| Tool | Description |
|
|
64
64
|
|---|---|
|
|
65
|
-
| `create_link` | Create a short link.
|
|
66
|
-
| `update_link` | Edit a link's title, source label, custom domain, password, archive state, or custom alias (e.g. `summer-sale` → `https://click.example.com/summer-sale`). |
|
|
65
|
+
| `create_link` | Create a short link. The destination URL is passed through untouched (include your own affiliate tag). |
|
|
66
|
+
| `update_link` | Edit a link's destination URL, title, source label, custom domain, password, archive state, or custom alias (e.g. `summer-sale` → `https://click.example.com/summer-sale`). Repointing the destination preserves the short code, alias, and click history. |
|
|
67
67
|
| `list_links` | List your links with click counts. |
|
|
68
68
|
| `get_link_stats` | Per-link analytics (by day, country, device, browser, hour, weekday). |
|
|
69
69
|
| `get_overview_stats` | Account-wide analytics. |
|
|
@@ -74,16 +74,22 @@ Most MCP clients use the same shape. Point them at the `npx -y @buygolinks/mcp`
|
|
|
74
74
|
| `list_affiliate_networks` / `create_affiliate_network` / `update_affiliate_network` / `delete_affiliate_network` | Manage affiliate networks. Each network exposes a per-network postback URL (`/postback/n/{token}`) the user pastes into the network's S2S settings. |
|
|
75
75
|
| `list_offers` / `create_offer` / `update_offer` / `delete_offer` | Manage destination URLs ("offers"). Optionally tied to an affiliate network so the right conversion-param name is propagated on outbound clicks. |
|
|
76
76
|
| `list_landers` / `create_lander` / `update_lander` / `delete_lander` | Manage landing pages. Lander URLs may contain `{clickid}` tokens which the resolver expands. |
|
|
77
|
-
| `
|
|
77
|
+
| `list_traffic_sources` / `create_traffic_source` / `update_traffic_source` / `delete_traffic_source` | Manage **traffic sources** — the platforms you buy traffic from (Facebook, TikTok, Google, …). Each has a `name`, optional `platform`, and `token_params` mapping the source's click macros onto sub1..sub5 (e.g. Facebook sub1 = Ad ID `{{ad.id}}`). `list_traffic_sources` returns a `presets` catalog. Campaigns reference one via `traffic_source_id`. |
|
|
78
|
+
| `list_tracking_pixels` / `create_tracking_pixel` / `update_tracking_pixel` / `delete_tracking_pixel` | Manage ad-platform pixels (Facebook, Google, TikTok, …). Each pixel has a `default_event` (e.g. `Purchase`, `Lead`, `CompletePayment`) and, for Facebook/TikTok, an optional server-side Conversions API `capi_token`. `list_tracking_pixels` also returns a `platforms` catalog with each platform's valid `events`, `defaultEvent`, and `capi` support. Pixels fire on short-link clicks and can be chosen as a campaign's conversion pixel. |
|
|
79
|
+
| `list_campaigns` / `create_campaign` / `get_campaign` / `update_campaign` / `delete_campaign` / `duplicate_campaign` | Manage campaigns. `duplicate_campaign` deep-copies a campaign (settings + full routing flow + offer/lander pools) into a fresh short link named `COPY - {name}` (`COPY (1) - …` when copying a copy); stats and the unique direct-link slug are not carried over. One campaign = one tracked short link + N offers (weighted) + N optional landers + a per-campaign postback URL + a pixel snippet. A campaign references one saved traffic source via `traffic_source_id` and an optional `conversion_pixel_id` + `conversion_event` that fires (server-side via CAPI when configured) when the campaign converts. Each campaign also returns a `click_url` (`…/click`) — the single, campaign-agnostic lander CTA link (same on every campaign, so one lander serves many) that always resolves to an offer (never a dead page); the campaign is read from the visitor's cookie or the ad's `?utm_campaign={direct_slug}` forwarded onto the CTA. Set `direct_slug` to run traffic straight to a lander with no tracker hop (`?utm_campaign={direct_slug}` resolves the campaign at `/click` and stitches via the property tag); `fallback_url` is the never-dead-end guarantee. |
|
|
78
80
|
| `set_campaign_offers` / `set_campaign_landers` | Replace the campaign's offer/lander split. Items are `{ offer_id, weight }` / `{ lander_id, weight }`. |
|
|
81
|
+
| `get_campaign_flow` / `set_campaign_flow` | Read/replace the campaign's **routing flow graph** (n8n-style). Nodes are `source` (one entry) / `lander` / `offer`; weighted edges go source→lander, source→offer, or lander→offer. The resolver walks this graph per click so a specific lander routes to its own specific offers — use it to split-test offers, divert a lander to a different offer, or insert a landing page before an offer. Saving also re-derives the flat offer/lander pools. |
|
|
79
82
|
| `get_campaign_stats` | Full campaign report: visits, lander views, offer clicks, conversions, revenue, cost, profit, CTR/CR/EPC/ROI, breakdowns, recent events. |
|
|
80
83
|
| `list_properties` / `create_property` / `get_property` / `update_property` / `delete_property` | Manage **properties** — GTM/GA-style site containers. One property = one universal tag (`/t.js?p={key}`) the user installs on every page of their site. Every page auto-reports pageviews, time-on-page, scroll, outbound clicks, and conversions; campaign traffic auto-attributes by click. `create_property` requires an active custom domain (the tag is served first-party from the user's own domain so it tracks on iOS/Safari) and returns the bound `tracking_domain` + install `snippet`. |
|
|
81
84
|
| `set_property_campaigns` | Link/unlink campaigns to a property (many-to-many, for the source split in reports). Items are `{ campaign_id }`. |
|
|
82
85
|
| `get_property_stats` | Full property report: page views, unique visitors, outbound, conversions, revenue, avg time/scroll, top pages, source split (organic vs campaign), country/device/browser/referrer breakdowns, recent events. |
|
|
86
|
+
| `list_property_pages` | Distinct pages seen across all the user's properties (most-viewed first): `{ url, path, title, property_name, views }`. Any page here can be used as a campaign **lander** — pass its `url` to `create_lander`, then wire that lander into a campaign flow with `set_campaign_flow`. |
|
|
87
|
+
| `get_tracker_overview` | Unified Tracker dashboard across **both** streams (sites + campaigns) in one call: totals (page views / visitors / visits / offer clicks / conversions / revenue), daily activity, country/device/browser breakdowns, per-property + per-campaign rollups, a **per-UTM-campaign** rollup (auto-detected Google `utm_*` tags), and the 50 most recent events. The "is everything running and getting traffic?" view. |
|
|
88
|
+
| `get_tracker_events` | Unified event log across both streams with full **search / filter / sort** over auto-detected UTM tags. `q` free-text, `stream`, `event`, exact `utm_*` filters, `sort`/`dir` (ts\|revenue\|utm_campaign\|utm_source\|country), pagination. Returns events + `facets` (distinct UTM values) + `total`. |
|
|
83
89
|
|
|
84
90
|
## Example prompts
|
|
85
91
|
|
|
86
|
-
> "Create a short link for `https://www.amazon.com/dp/B0CFG6RB6T` with the title 'Anker GaN charger'."
|
|
92
|
+
> "Create a short link for `https://www.amazon.com/dp/B0CFG6RB6T?tag=mytag-20` with the title 'Anker GaN charger'."
|
|
87
93
|
|
|
88
94
|
> "How many clicks did I get last week? Which country drove the most?"
|
|
89
95
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buygolinks/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"description": "MCP server for buygolinks.com — create and manage branded short links with Amazon affiliate geo-routing from any MCP-compatible AI agent (Claude Desktop, Cursor, Cline, Continue, Hermes).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/index.js
CHANGED
|
@@ -48,7 +48,7 @@ const TOOLS = [
|
|
|
48
48
|
{
|
|
49
49
|
name: 'create_link',
|
|
50
50
|
description:
|
|
51
|
-
'Create a new branded short link on buygolinks.com.
|
|
51
|
+
'Create a new branded short link on buygolinks.com. The link redirects to exactly the destination URL you provide — for Amazon links, include your own Associates tag in the URL (e.g. ?tag=yourtag-20); buygolinks passes it through untouched and does not rewrite or geo-route Amazon URLs. By default, the link is created under the user\'s first active custom domain (if any); pass `domain_id` to pin it to a specific one. Returns the short URL and link metadata (including `short_domain`, the hostname the link will resolve at).',
|
|
52
52
|
inputSchema: {
|
|
53
53
|
type: 'object',
|
|
54
54
|
properties: {
|
|
@@ -58,11 +58,11 @@ const TOOLS = [
|
|
|
58
58
|
},
|
|
59
59
|
title: {
|
|
60
60
|
type: 'string',
|
|
61
|
-
description: 'Optional human-readable title for the link.
|
|
61
|
+
description: 'Optional human-readable title for the link.',
|
|
62
62
|
},
|
|
63
63
|
source_label: {
|
|
64
64
|
type: 'string',
|
|
65
|
-
description: 'Optional source label (campaign tag)
|
|
65
|
+
description: 'Optional source label (campaign tag) for your own attribution.',
|
|
66
66
|
},
|
|
67
67
|
domain_id: {
|
|
68
68
|
type: 'number',
|
|
@@ -75,11 +75,12 @@ const TOOLS = [
|
|
|
75
75
|
{
|
|
76
76
|
name: 'update_link',
|
|
77
77
|
description:
|
|
78
|
-
'Update an existing short link. Supports renaming (title), changing the campaign source label, swapping the branded domain it resolves under (domain_id), setting a memorable custom alias (alias), archiving/unarchiving, and password-protecting the link. Pass `alias` to give the link a memorable URL slug on its custom domain (e.g. alias="summer-sale" → https://click.example.com/summer-sale). Aliases require the link to be on a custom domain. Setting `alias` REPLACES any previous alias — the old alias stops resolving immediately and is freed for any other link to claim. The link\'s original random short code (link.code) keeps working forever regardless of alias changes. Pass alias="" or alias=null to remove the alias. Changing the link\'s `domain_id` clears the alias automatically. To set or change the password, pass `password` as a non-empty string. To remove password protection, pass an empty string for `password`. To move the link back to the platform default (buygolinks.com), pass `domain_id: null`. Returns the updated link row including `short_domain`, `alias`, and `password_protected` (boolean — the plaintext is never returned).',
|
|
78
|
+
'Update an existing short link. Supports changing where it redirects (destination_url), renaming (title), changing the campaign source label, swapping the branded domain it resolves under (domain_id), setting a memorable custom alias (alias), archiving/unarchiving, and password-protecting the link. Pass `destination_url` to repoint the link at a new target — the short code, alias, and click history are preserved; only the redirect destination moves (the ASIN is re-extracted automatically for Amazon /dp/ URLs). Pass `alias` to give the link a memorable URL slug on its custom domain (e.g. alias="summer-sale" → https://click.example.com/summer-sale). Aliases require the link to be on a custom domain. Setting `alias` REPLACES any previous alias — the old alias stops resolving immediately and is freed for any other link to claim. The link\'s original random short code (link.code) keeps working forever regardless of alias changes. Pass alias="" or alias=null to remove the alias. Changing the link\'s `domain_id` clears the alias automatically. To set or change the password, pass `password` as a non-empty string. To remove password protection, pass an empty string for `password`. To move the link back to the platform default (buygolinks.com), pass `domain_id: null`. Returns the updated link row including `short_domain`, `alias`, and `password_protected` (boolean — the plaintext is never returned).',
|
|
79
79
|
inputSchema: {
|
|
80
80
|
type: 'object',
|
|
81
81
|
properties: {
|
|
82
82
|
link_id: { type: 'number', description: 'The numeric id of the link (from list_links / create_link).' },
|
|
83
|
+
destination_url: { type: 'string', description: 'New destination URL the short link redirects to (must be http(s)). The short code, alias, and click history are preserved.' },
|
|
83
84
|
title: { type: 'string', description: 'New display title.' },
|
|
84
85
|
source_label: { type: 'string', description: 'New source label (campaign tag).' },
|
|
85
86
|
archived: { type: 'boolean', description: 'Set true to archive the link, false to unarchive.' },
|
|
@@ -126,7 +127,7 @@ const TOOLS = [
|
|
|
126
127
|
{
|
|
127
128
|
name: 'get_overview_stats',
|
|
128
129
|
description:
|
|
129
|
-
'Get account-wide
|
|
130
|
+
'Get account-wide analytics for the user\'s branded short LINKS only: total link CLICKS, clicks by day, top countries, devices, browsers, referrers, peak hour and weekday. This covers the Links section ONLY — it does NOT include Tracker page views, site-property traffic, or campaign visits/conversions. For PAGE VIEWS (site properties) or campaign traffic use the Tracker tools instead: get_tracker_overview (clicks-vs-pageviews unified rollup), list_properties, or get_property_stats. If the user asks for "clicks AND page views" together, call BOTH this tool and get_tracker_overview and combine them — they read from separate event streams. Use this for "how are my links doing" type questions.',
|
|
130
131
|
inputSchema: {
|
|
131
132
|
type: 'object',
|
|
132
133
|
properties: {
|
|
@@ -193,7 +194,7 @@ const TOOLS = [
|
|
|
193
194
|
{
|
|
194
195
|
name: 'create_affiliate_network',
|
|
195
196
|
description:
|
|
196
|
-
'Add a new affiliate network. The server mints a per-network postback URL (https://buygolinks.com/postback/n/{token}) the user pastes into the network. Returns the network row including postback_url.',
|
|
197
|
+
'Add a new affiliate network. The server mints a per-network postback URL (https://buygolinks.com/postback/n/{token}) the user pastes into the network. The postback accepts ?{conversion_param}={clickid}&{payout_param}={payout}&{currency_param}={currency}&{txid_param}={txid} — payout, currency, and txid are optional. Returns the network row including postback_url.',
|
|
197
198
|
inputSchema: {
|
|
198
199
|
type: 'object',
|
|
199
200
|
properties: {
|
|
@@ -201,6 +202,7 @@ const TOOLS = [
|
|
|
201
202
|
conversion_param: { type: 'string', description: 'What the network calls the click id in its postback (default "clickid").' },
|
|
202
203
|
payout_param: { type: 'string', description: 'What the network calls the payout amount (default "payout").' },
|
|
203
204
|
currency_param: { type: 'string', description: 'What the network calls the currency code (default "currency").' },
|
|
205
|
+
txid_param: { type: 'string', description: 'What the network calls the transaction/order id in its postback (default "txid"). Stored on the conversion for reconciliation/dedupe.' },
|
|
204
206
|
},
|
|
205
207
|
required: ['name'],
|
|
206
208
|
},
|
|
@@ -216,6 +218,7 @@ const TOOLS = [
|
|
|
216
218
|
conversion_param: { type: 'string' },
|
|
217
219
|
payout_param: { type: 'string' },
|
|
218
220
|
currency_param: { type: 'string' },
|
|
221
|
+
txid_param: { type: 'string' },
|
|
219
222
|
archived: { type: 'boolean' },
|
|
220
223
|
},
|
|
221
224
|
required: ['network_id'],
|
|
@@ -240,7 +243,7 @@ const TOOLS = [
|
|
|
240
243
|
{
|
|
241
244
|
name: 'create_offer',
|
|
242
245
|
description:
|
|
243
|
-
'Create an offer (a destination URL: affiliate landing page, product page, etc).
|
|
246
|
+
'Create an offer (a destination URL: affiliate landing page, product page, etc). The URL is used exactly as given when redirecting — include any affiliate tag yourself. Link the offer to an affiliate_network_id so the resolver knows which postback param name to use when redirecting (e.g. some networks expect ?aff_sub=… instead of ?clickid=…).',
|
|
244
247
|
inputSchema: {
|
|
245
248
|
type: 'object',
|
|
246
249
|
properties: {
|
|
@@ -319,6 +322,105 @@ const TOOLS = [
|
|
|
319
322
|
inputSchema: { type: 'object', properties: { lander_id: { type: 'number' } }, required: ['lander_id'] },
|
|
320
323
|
},
|
|
321
324
|
|
|
325
|
+
// ─── Traffic sources ────────────────────────────────────────────────────
|
|
326
|
+
{
|
|
327
|
+
name: 'list_traffic_sources',
|
|
328
|
+
description:
|
|
329
|
+
'List the user\'s traffic sources (the platforms they buy traffic from — Facebook, TikTok, Google, …). Each source has a `name`, an optional `platform` (pixel-platform key), and `token_params`: how the source\'s click macros map onto the Google UTM params the resolver captures (utm_source/medium/campaign/term/content), e.g. Facebook utm_campaign = `{{campaign.id}}`, utm_content = Ad ID `{{ad.id}}`. The response also includes a `presets` catalog of ready-made sources with standard macros. Campaigns reference a source via `traffic_source_id` (see create_campaign / update_campaign).',
|
|
330
|
+
inputSchema: { type: 'object', properties: {} },
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
name: 'create_traffic_source',
|
|
334
|
+
description:
|
|
335
|
+
'Create a traffic source. `token_params` is an array of `{ slot, label, token }` where slot is one of utm_source/utm_medium/utm_campaign/utm_term/utm_content, label is a human name (e.g. "Campaign ID"), and token is the platform macro (e.g. "{{campaign.id}}"). Use `list_traffic_sources` first to copy a preset\'s token_params for the big platforms.',
|
|
336
|
+
inputSchema: {
|
|
337
|
+
type: 'object',
|
|
338
|
+
properties: {
|
|
339
|
+
name: { type: 'string', description: 'e.g. "Facebook".' },
|
|
340
|
+
platform: { type: ['string', 'null'], description: 'Optional pixel-platform key (facebook, google_ads, tiktok, bing, …) for the icon + conversion-pixel suggestion.' },
|
|
341
|
+
token_params: {
|
|
342
|
+
type: 'array',
|
|
343
|
+
items: {
|
|
344
|
+
type: 'object',
|
|
345
|
+
properties: {
|
|
346
|
+
slot: { type: 'string', enum: ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'] },
|
|
347
|
+
label: { type: 'string' },
|
|
348
|
+
token: { type: 'string' },
|
|
349
|
+
},
|
|
350
|
+
required: ['slot'],
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
required: ['name'],
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: 'update_traffic_source',
|
|
359
|
+
description: 'Update a traffic source\'s name, platform, token_params, or archive state. token_params replaces the whole set.',
|
|
360
|
+
inputSchema: {
|
|
361
|
+
type: 'object',
|
|
362
|
+
properties: {
|
|
363
|
+
traffic_source_id: { type: 'number' },
|
|
364
|
+
name: { type: 'string' },
|
|
365
|
+
platform: { type: ['string', 'null'] },
|
|
366
|
+
token_params: { type: 'array', items: { type: 'object', properties: { slot: { type: 'string' }, label: { type: 'string' }, token: { type: 'string' } }, required: ['slot'] } },
|
|
367
|
+
archived: { type: 'boolean' },
|
|
368
|
+
},
|
|
369
|
+
required: ['traffic_source_id'],
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: 'delete_traffic_source',
|
|
374
|
+
description: 'Delete a traffic source. Campaigns using it keep their data but their `traffic_source_id` is cleared.',
|
|
375
|
+
inputSchema: { type: 'object', properties: { traffic_source_id: { type: 'number' } }, required: ['traffic_source_id'] },
|
|
376
|
+
},
|
|
377
|
+
|
|
378
|
+
// ─── Tracking pixels ────────────────────────────────────────────────────
|
|
379
|
+
{
|
|
380
|
+
name: 'list_tracking_pixels',
|
|
381
|
+
description:
|
|
382
|
+
'List the user\'s tracking pixels (Facebook, Google Ads, TikTok, etc). Each pixel has a platform, pixel_id, a default_event it fires, an active flag, and has_capi_token (whether a server-side Conversions API token is set — the token itself is never returned). The response also includes a `platforms` catalog with each platform\'s valid `events`, its `defaultEvent`, and `capi` (whether server-side conversions are supported). Pixels fire on short-link clicks (account-wide by default, or per-link selection) and can be chosen as a campaign\'s conversion pixel.',
|
|
383
|
+
inputSchema: { type: 'object', properties: {} },
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
name: 'create_tracking_pixel',
|
|
387
|
+
description:
|
|
388
|
+
'Create a tracking pixel. `platform` is one of: facebook, google_ads, google_analytics, google_tag_manager, linkedin, twitter, tiktok, pinterest, snapchat, reddit, quora, bing, adroll. `default_event` must be a valid event for that platform (see the `platforms` catalog from list_tracking_pixels) — defaults to the platform default (e.g. Facebook "PageView"). For Facebook and TikTok you can pass a `capi_token` (server-side Conversions API access token) so campaign conversions fire server-to-server.',
|
|
389
|
+
inputSchema: {
|
|
390
|
+
type: 'object',
|
|
391
|
+
properties: {
|
|
392
|
+
platform: { type: 'string', description: 'Pixel platform key (facebook, google_ads, tiktok, …).' },
|
|
393
|
+
pixel_id: { type: 'string', description: 'The platform\'s pixel/dataset id.' },
|
|
394
|
+
label: { type: 'string', description: 'Optional friendly name.' },
|
|
395
|
+
default_event: { type: 'string', description: 'Event this pixel fires by default (must be valid for the platform).' },
|
|
396
|
+
capi_token: { type: 'string', description: 'Optional server-side Conversions API access token (Facebook/TikTok only).' },
|
|
397
|
+
active: { type: 'boolean', description: 'Whether the pixel is active. Defaults to true.' },
|
|
398
|
+
},
|
|
399
|
+
required: ['platform', 'pixel_id'],
|
|
400
|
+
},
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
name: 'update_tracking_pixel',
|
|
404
|
+
description: 'Update a tracking pixel\'s pixel_id, label, default_event, active flag, or CAPI token. Pass capi_token as an empty string or null to clear the stored token.',
|
|
405
|
+
inputSchema: {
|
|
406
|
+
type: 'object',
|
|
407
|
+
properties: {
|
|
408
|
+
pixel_id_row: { type: 'number', description: 'The id of the tracking_pixels row to update (from list_tracking_pixels).' },
|
|
409
|
+
pixel_id: { type: 'string', description: 'New platform pixel id.' },
|
|
410
|
+
label: { type: 'string' },
|
|
411
|
+
default_event: { type: 'string', description: 'New default event (must be valid for the pixel\'s platform).' },
|
|
412
|
+
capi_token: { type: ['string', 'null'], description: 'New Conversions API token, or empty/null to clear.' },
|
|
413
|
+
active: { type: 'boolean' },
|
|
414
|
+
},
|
|
415
|
+
required: ['pixel_id_row'],
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
name: 'delete_tracking_pixel',
|
|
420
|
+
description: 'Delete a tracking pixel. It is removed from any per-link selections and any campaign that used it as its conversion pixel (the campaign\'s conversion_pixel_id is cleared).',
|
|
421
|
+
inputSchema: { type: 'object', properties: { pixel_id_row: { type: 'number' } }, required: ['pixel_id_row'] },
|
|
422
|
+
},
|
|
423
|
+
|
|
322
424
|
// ─── Tracker: campaigns ─────────────────────────────────────────────────
|
|
323
425
|
{
|
|
324
426
|
name: 'list_campaigns',
|
|
@@ -329,17 +431,23 @@ const TOOLS = [
|
|
|
329
431
|
{
|
|
330
432
|
name: 'create_campaign',
|
|
331
433
|
description:
|
|
332
|
-
'Create a campaign. A campaign mints one short link (campaign.code) that the user pastes into Facebook / TikTok / etc. The resolver mints a click_id per visit, optionally routes through a lander (weighted), then redirects to an offer (weighted). Conversions come back via the campaign\'s postback URL or the affiliate network\'s postback URL. You can optionally attach offer_ids / lander_ids inline with matching offer_weights / lander_weights arrays for a one-call setup.',
|
|
434
|
+
'Create a campaign. A campaign mints one short link (campaign.code) that the user pastes into Facebook / TikTok / etc. The resolver mints a click_id per visit, optionally routes through a lander (weighted), then redirects to an offer (weighted). The response also includes a `click_url` (`…/click`) — the single, campaign-agnostic DEFINED lander click URL the user pastes into their lander\'s CTA button. It\'s the same on every campaign, so one lander can be reused across many; the campaign is resolved at click time from the visitor\'s `bgl_clk_{code}` cookie (short-link traffic) or the ad\'s `?utm_campaign={direct_slug}` that the lander forwards onto the CTA (direct-link). It always resolves to an offer (chosen by the flow graph), never a lander, never a dead page. For DIRECT-LINK campaigns (ad points straight at the lander, no tracker hop), set `direct_slug`, put `?utm_campaign={direct_slug}` on the ad URL, and have the lander forward that utm_campaign onto the CTA — `/click` resolves the campaign from it and the property tag stitches the page view too. `fallback_url` guarantees the click never dead-ends. Conversions come back via the campaign\'s postback URL or the affiliate network\'s postback URL. You can optionally attach offer_ids / lander_ids inline with matching offer_weights / lander_weights arrays for a one-call setup.',
|
|
333
435
|
inputSchema: {
|
|
334
436
|
type: 'object',
|
|
335
437
|
properties: {
|
|
336
438
|
name: { type: 'string' },
|
|
337
|
-
|
|
439
|
+
traffic_source_id: { type: ['number', 'null'], description: 'Id of one of the user\'s saved traffic sources (from list_traffic_sources) to attribute this campaign to. Preferred over the legacy `traffic_source` string. Mirrors the source name into the display label.' },
|
|
440
|
+
traffic_source: { type: 'string', description: 'Legacy structured traffic source key — one of: facebook, instagram, google, youtube, tiktok, microsoft, linkedin, twitter, pinterest, snapchat, reddit, native, email, organic, other. Prefer `traffic_source_id`.' },
|
|
441
|
+
traffic_source_label: { type: 'string', description: 'Free text display label.' },
|
|
338
442
|
country: { type: 'string', description: 'ISO2 default geo.' },
|
|
339
443
|
cost_model: { type: 'string', enum: ['not_tracked', 'cpc', 'cpm'] },
|
|
340
444
|
cost_value: { type: 'number' },
|
|
341
445
|
transition_mode: { type: 'string', enum: ['302', 'meta_refresh'] },
|
|
342
446
|
domain_id: { type: ['number', 'null'], description: 'Optional id of one of the user\'s active custom domains (from list_domains). The campaign\'s short link will resolve at that hostname. If omitted, defaults to the user\'s first active custom domain, or buygolinks.com if they have none. Pass null to force the platform default.' },
|
|
447
|
+
conversion_pixel_id: { type: ['number', 'null'], description: 'Optional id of one of the user\'s tracking pixels (from list_tracking_pixels) to fire when this campaign converts (server-side via the pixel\'s CAPI token when set). Pass null for no conversion pixel.' },
|
|
448
|
+
conversion_event: { type: 'string', description: 'Event name to fire on conversion (e.g. "Purchase", "Lead", "CompletePayment"). Must be a valid event for the conversion pixel\'s platform; defaults to that pixel\'s default_event.' },
|
|
449
|
+
direct_slug: { type: ['string', 'null'], description: 'Optional direct-link tag, unique per user (lowercased alnum + dashes, e.g. "bsu-fb-cold"). For running traffic STRAIGHT to a lander with no tracker hop: put `?utm_campaign={direct_slug}` on the ad URL and the property tag stitches the page view to this campaign. Returns 409 (direct_slug_taken) if another campaign already uses it. Pass null to clear.' },
|
|
450
|
+
fallback_url: { type: ['string', 'null'], description: 'Optional URL the click falls back to when the flow yields no live offer — the never-dead-end guarantee. Pass null to use the platform default.' },
|
|
343
451
|
offer_ids: { type: 'array', items: { type: 'number' } },
|
|
344
452
|
offer_weights: { type: 'array', items: { type: 'number' } },
|
|
345
453
|
lander_ids: { type: 'array', items: { type: 'number' } },
|
|
@@ -355,18 +463,24 @@ const TOOLS = [
|
|
|
355
463
|
},
|
|
356
464
|
{
|
|
357
465
|
name: 'update_campaign',
|
|
358
|
-
description: 'Update a campaign\'s name, country, cost model/value, transition mode, traffic source label, branded domain, or archive state. Use set_campaign_offers / set_campaign_landers to change the destination wiring. Pass domain_id to move the campaign\'s short link to a different custom domain, or null to send it back to buygolinks.com.',
|
|
466
|
+
description: 'Update a campaign\'s name, structured traffic source, country, cost model/value, transition mode, traffic source label, branded domain, conversion pixel/event, direct-link slug, fallback URL, or archive state. Use set_campaign_offers / set_campaign_landers (or set_campaign_flow) to change the destination wiring. Pass domain_id to move the campaign\'s short link to a different custom domain, or null to send it back to buygolinks.com.',
|
|
359
467
|
inputSchema: {
|
|
360
468
|
type: 'object',
|
|
361
469
|
properties: {
|
|
362
470
|
campaign_id: { type: 'number' },
|
|
363
471
|
name: { type: 'string' },
|
|
472
|
+
traffic_source_id: { type: ['number', 'null'], description: 'Id of a saved traffic source (from list_traffic_sources), or null to clear. Preferred over `traffic_source`.' },
|
|
473
|
+
traffic_source: { type: ['string', 'null'], description: 'Legacy structured traffic source key or null to clear.' },
|
|
364
474
|
traffic_source_label: { type: 'string' },
|
|
365
475
|
country: { type: 'string' },
|
|
366
476
|
cost_model: { type: 'string', enum: ['not_tracked', 'cpc', 'cpm'] },
|
|
367
477
|
cost_value: { type: 'number' },
|
|
368
478
|
transition_mode: { type: 'string', enum: ['302', 'meta_refresh'] },
|
|
369
479
|
domain_id: { type: ['number', 'null'], description: 'Id of one of the user\'s active custom domains, or null to move the campaign back to buygolinks.com.' },
|
|
480
|
+
conversion_pixel_id: { type: ['number', 'null'], description: 'Id of a tracking pixel to fire on conversion, or null to clear.' },
|
|
481
|
+
conversion_event: { type: 'string', description: 'Event the conversion pixel fires (must be valid for that pixel\'s platform).' },
|
|
482
|
+
direct_slug: { type: ['string', 'null'], description: 'Direct-link tag for utm_campaign-based stitching (unique per user), or null to clear. Returns 409 (direct_slug_taken) on collision.' },
|
|
483
|
+
fallback_url: { type: ['string', 'null'], description: 'Never-dead-end fallback URL when no live offer exists, or null to use the platform default.' },
|
|
370
484
|
archived: { type: 'boolean' },
|
|
371
485
|
},
|
|
372
486
|
required: ['campaign_id'],
|
|
@@ -377,6 +491,11 @@ const TOOLS = [
|
|
|
377
491
|
description: 'Delete a campaign. The short link stops resolving immediately. Tracker events for this campaign are also deleted (cascade).',
|
|
378
492
|
inputSchema: { type: 'object', properties: { campaign_id: { type: 'number' } }, required: ['campaign_id'] },
|
|
379
493
|
},
|
|
494
|
+
{
|
|
495
|
+
name: 'duplicate_campaign',
|
|
496
|
+
description: 'Deep-copy a campaign: clones the campaign settings plus its full routing flow (source → landers → offers graph and split-test weights) and its offer/lander pools. The copy gets a fresh short link + postback token and a name like "COPY - {original}" (or "COPY (1) - {original}", "COPY (2) - …" when copying a copy). Stats are NOT copied; the direct-link slug is left blank (it must be unique). Returns the new campaign, including its short_url/click_url. Useful for spinning up a new geo/angle variant of a working campaign without rebuilding the flow.',
|
|
497
|
+
inputSchema: { type: 'object', properties: { campaign_id: { type: 'number' } }, required: ['campaign_id'] },
|
|
498
|
+
},
|
|
380
499
|
{
|
|
381
500
|
name: 'set_campaign_offers',
|
|
382
501
|
description: 'Replace the campaign\'s offer split with the supplied items. Each item is { offer_id, weight }. Weight is an integer; higher = more traffic. To direct all traffic to one offer use weight=100 for that offer and don\'t include the others.',
|
|
@@ -415,6 +534,54 @@ const TOOLS = [
|
|
|
415
534
|
required: ['campaign_id', 'items'],
|
|
416
535
|
},
|
|
417
536
|
},
|
|
537
|
+
{
|
|
538
|
+
name: 'get_campaign_flow',
|
|
539
|
+
description:
|
|
540
|
+
'Get the campaign\'s routing flow graph (n8n-style). Returns { flow: { nodes, edges }, offers, landers }. Nodes are { id, kind: "source"|"lander"|"offer", ref_id, pos_x, pos_y, offer?, lander? } — exactly one "source" node (the traffic entry). Edges are { id, from_node_id, to_node_id, weight } and only go source→lander, source→offer (direct), or lander→offer; weight is the split-test weight. This graph is what the resolver walks per click, letting a specific lander route to its own specific offers.',
|
|
541
|
+
inputSchema: {
|
|
542
|
+
type: 'object',
|
|
543
|
+
properties: { campaign_id: { type: 'number' } },
|
|
544
|
+
required: ['campaign_id'],
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
name: 'set_campaign_flow',
|
|
549
|
+
description:
|
|
550
|
+
'Replace the campaign\'s routing flow graph. Body: { nodes: [{ tmp_id, kind, ref_id, pos_x, pos_y }], edges: [{ from, to, weight }] }. tmp_id is any client-stable string key; edges reference nodes by tmp_id. Provide exactly one node with kind="source" (ref_id null) as the traffic entry; "lander" / "offer" nodes set ref_id to a lander/offer id you own. Edges must go source→lander, source→offer, or lander→offer. weight is an integer split-test weight (higher = more traffic). Saving also re-derives the flat offer/lander pools, so legacy reports stay consistent. Use this to add a second offer for a split test, divert a lander to a different offer, or insert a landing page before an offer.',
|
|
551
|
+
inputSchema: {
|
|
552
|
+
type: 'object',
|
|
553
|
+
properties: {
|
|
554
|
+
campaign_id: { type: 'number' },
|
|
555
|
+
nodes: {
|
|
556
|
+
type: 'array',
|
|
557
|
+
items: {
|
|
558
|
+
type: 'object',
|
|
559
|
+
properties: {
|
|
560
|
+
tmp_id: { type: 'string' },
|
|
561
|
+
kind: { type: 'string', enum: ['source', 'lander', 'offer'] },
|
|
562
|
+
ref_id: { type: ['number', 'null'] },
|
|
563
|
+
pos_x: { type: 'number' },
|
|
564
|
+
pos_y: { type: 'number' },
|
|
565
|
+
},
|
|
566
|
+
required: ['tmp_id', 'kind'],
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
edges: {
|
|
570
|
+
type: 'array',
|
|
571
|
+
items: {
|
|
572
|
+
type: 'object',
|
|
573
|
+
properties: {
|
|
574
|
+
from: { type: 'string' },
|
|
575
|
+
to: { type: 'string' },
|
|
576
|
+
weight: { type: 'number' },
|
|
577
|
+
},
|
|
578
|
+
required: ['from', 'to'],
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
required: ['campaign_id', 'nodes', 'edges'],
|
|
583
|
+
},
|
|
584
|
+
},
|
|
418
585
|
{
|
|
419
586
|
name: 'get_campaign_stats',
|
|
420
587
|
description:
|
|
@@ -433,13 +600,13 @@ const TOOLS = [
|
|
|
433
600
|
{
|
|
434
601
|
name: 'list_properties',
|
|
435
602
|
description:
|
|
436
|
-
'List the user\'s properties with quick-look stats (page_views / unique_visitors / conversions / revenue). A property is a whole-site container: the user installs ONE universal tag (returned as `snippet`) on every page of their site and every page auto-reports pageviews, time-on-page, scroll depth, outbound clicks, and conversions. Page events auto-attribute to a campaign when a visitor arrives carrying that campaign\'s click.',
|
|
603
|
+
'List the user\'s properties with quick-look stats (page_views / unique_visitors / conversions / revenue). Best one-call tool for "page views per property" / "page views for each site". A property is a whole-site container: the user installs ONE universal tag (returned as `snippet`) on every page of their site and every page auto-reports pageviews, time-on-page, scroll depth, outbound clicks, and conversions. Page events auto-attribute to a campaign when a visitor arrives carrying that campaign\'s click.',
|
|
437
604
|
inputSchema: { type: 'object', properties: {} },
|
|
438
605
|
},
|
|
439
606
|
{
|
|
440
607
|
name: 'create_property',
|
|
441
608
|
description:
|
|
442
|
-
'Create a property (a GTM/GA-style site container). Returns the property with its public property_key, the `tracking_domain` it\'s bound to, and an install `snippet` — a single <script> tag the user pastes into the <head> of every page of their site. REQUIRES an active custom domain: the tag is served from the user\'s own domain so it stays first-party to their site, which is required for tracking to work on iOS/Safari (a tag loaded cross-site from buygolinks.com gets its visitor cookie partitioned/blocked). Defaults to the user\'s first active custom domain; pass `domain_id` (from list_domains) to pin a specific one. If the user has no active custom domain, returns error `domain_required` — have them add one via create_domain first. To record conversions, the site sets window.bglConversion = true (optionally window.bglRevenue) on a thank-you page, or calls window.bglFireConversion(revenue).',
|
|
609
|
+
'Create a property (a GTM/GA-style site container). Returns the property with its public property_key, the `tracking_domain` it\'s bound to, and an install `snippet` — a single GA/Facebook-style inline <script> block (a self-invoking function that sets up a conversion-queue stub and injects the async tag) the user pastes into the <head> of every page of their site. REQUIRES an active custom domain: the tag is served from the user\'s own domain so it stays first-party to their site, which is required for tracking to work on iOS/Safari (a tag loaded cross-site from buygolinks.com gets its visitor cookie partitioned/blocked). Defaults to the user\'s first active custom domain; pass `domain_id` (from list_domains) to pin a specific one. If the user has no active custom domain, returns error `domain_required` — have them add one via create_domain first. To record conversions, the site sets window.bglConversion = true (optionally window.bglRevenue) on a thank-you page, or calls window.bglFireConversion(revenue) — the queue stub makes that call safe even before the async tag finishes loading.',
|
|
443
610
|
inputSchema: {
|
|
444
611
|
type: 'object',
|
|
445
612
|
properties: {
|
|
@@ -497,7 +664,7 @@ const TOOLS = [
|
|
|
497
664
|
{
|
|
498
665
|
name: 'get_property_stats',
|
|
499
666
|
description:
|
|
500
|
-
'Get a property\'s GA-style report: totals (page_views / unique_visitors / outbound / conversions / revenue / avg_time_on_page_ms / avg_scroll_pct / CR), by_day, by_page (top paths with views+conversions+revenue), by_source (organic vs each stitched campaign), by_country / by_device / by_browser / by_referrer, and the 50 most recent events.',
|
|
667
|
+
'Get a single property\'s GA-style PAGE VIEW report: totals (page_views / unique_visitors / outbound / conversions / revenue / avg_time_on_page_ms / avg_scroll_pct / CR), by_day, by_page (top paths with views+conversions+revenue), by_source (organic vs each stitched campaign), by_country / by_device / by_browser / by_referrer, and the 50 most recent events.',
|
|
501
668
|
inputSchema: {
|
|
502
669
|
type: 'object',
|
|
503
670
|
properties: {
|
|
@@ -507,10 +674,50 @@ const TOOLS = [
|
|
|
507
674
|
required: ['property_id'],
|
|
508
675
|
},
|
|
509
676
|
},
|
|
677
|
+
{
|
|
678
|
+
name: 'list_property_pages',
|
|
679
|
+
description:
|
|
680
|
+
'List the distinct pages observed across ALL the user\'s properties (the pageview stream), most-viewed first. Each entry is `{ url, path, title, property_id, property_name, views }`. Any of these pages can be used as a campaign LANDER — pass its `url` to `create_lander` (the page title makes a good lander name) and then wire the resulting lander into a campaign flow via `set_campaign_flow`. Use this to turn a real page on the user\'s site into a lander without retyping its URL.',
|
|
681
|
+
inputSchema: { type: 'object', properties: {} },
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
name: 'get_tracker_overview',
|
|
685
|
+
description:
|
|
686
|
+
'PAGE VIEWS live here. Get the unified Tracker dashboard rollup across BOTH event streams (site properties + campaigns) in one call. Returns totals (page_views / unique_visitors / visits / offer_clicks / conversions / revenue), a merged daily activity series (by_day), dimension breakdowns (by_country / by_device / by_browser), per-property and per-campaign rollups (the per-property rollup gives page_views for each site/property), a per-UTM-campaign rollup (utm_campaigns — auto-detected Google utm_source/medium/campaign/content tags with events/conversions/revenue), and the 50 most recent events across both streams. This is the go-to tool for "page views", "page views per property", "campaign traffic", and "is everything running and getting traffic?". NOTE: page_views here is Tracker site traffic and is SEPARATE from branded short-link clicks (get_overview_stats). If the user asks for "clicks AND page views" together, call BOTH this tool and get_overview_stats and combine them.',
|
|
687
|
+
inputSchema: {
|
|
688
|
+
type: 'object',
|
|
689
|
+
properties: {
|
|
690
|
+
days: { type: 'number', description: 'Window size in days (1–90). Defaults to 14.' },
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
name: 'get_tracker_events',
|
|
696
|
+
description:
|
|
697
|
+
'Query the unified Tracker event log across BOTH streams (site properties + campaigns), with full search / filter / sort over auto-detected Google UTM tags. Every event carries utm_source / utm_medium / utm_campaign / utm_term / utm_content. Use `q` for free-text search (matches any utm value, country, device, browser, referrer, source name, event type); `stream` to scope to all|property|campaign; `event` to filter by event_type; the utm_* params for exact-match filtering; and `sort`+`dir` to order by ts|revenue|utm_campaign|utm_source|country. Returns the matching page of events plus `facets` (distinct utm values present in the window) to drive filter pickers, and `total` for the full match count.',
|
|
698
|
+
inputSchema: {
|
|
699
|
+
type: 'object',
|
|
700
|
+
properties: {
|
|
701
|
+
days: { type: 'number', description: 'Window size in days (1–90). Defaults to 14.' },
|
|
702
|
+
q: { type: 'string', description: 'Free-text search across utm_*, country, device, browser, referrer, source name, event type.' },
|
|
703
|
+
stream: { type: 'string', enum: ['all', 'property', 'campaign'], description: 'Scope to one stream. Defaults to all.' },
|
|
704
|
+
event: { type: 'string', description: 'Exact event_type filter, e.g. page_view, outbound, conversion, visit, lander_view, offer_click.' },
|
|
705
|
+
utm_source: { type: 'string', description: 'Exact utm_source filter.' },
|
|
706
|
+
utm_medium: { type: 'string', description: 'Exact utm_medium filter.' },
|
|
707
|
+
utm_campaign: { type: 'string', description: 'Exact utm_campaign filter.' },
|
|
708
|
+
utm_term: { type: 'string', description: 'Exact utm_term filter.' },
|
|
709
|
+
utm_content: { type: 'string', description: 'Exact utm_content filter.' },
|
|
710
|
+
sort: { type: 'string', enum: ['ts', 'revenue', 'utm_campaign', 'utm_source', 'country'], description: 'Sort field. Defaults to ts.' },
|
|
711
|
+
dir: { type: 'string', enum: ['asc', 'desc'], description: 'Sort direction. Defaults to desc.' },
|
|
712
|
+
limit: { type: 'number', description: 'Page size (1–500). Defaults to 200.' },
|
|
713
|
+
offset: { type: 'number', description: 'Pagination offset. Defaults to 0.' },
|
|
714
|
+
},
|
|
715
|
+
},
|
|
716
|
+
},
|
|
510
717
|
];
|
|
511
718
|
|
|
512
719
|
const server = new Server(
|
|
513
|
-
{ name: 'buygolinks', version: '0.
|
|
720
|
+
{ name: 'buygolinks', version: '0.11.0' },
|
|
514
721
|
{ capabilities: { tools: {} } },
|
|
515
722
|
);
|
|
516
723
|
|
|
@@ -536,6 +743,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
536
743
|
}
|
|
537
744
|
case 'update_link': {
|
|
538
745
|
const body = {};
|
|
746
|
+
if (typeof args.destination_url === 'string') body.destination_url = args.destination_url;
|
|
539
747
|
if (typeof args.title === 'string') body.title = args.title;
|
|
540
748
|
if (typeof args.source_label === 'string') body.source_label = args.source_label;
|
|
541
749
|
if (typeof args.archived === 'boolean') body.archived = args.archived;
|
|
@@ -597,6 +805,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
597
805
|
if (typeof args.conversion_param === 'string') body.conversion_param = args.conversion_param;
|
|
598
806
|
if (typeof args.payout_param === 'string') body.payout_param = args.payout_param;
|
|
599
807
|
if (typeof args.currency_param === 'string') body.currency_param = args.currency_param;
|
|
808
|
+
if (typeof args.txid_param === 'string') body.txid_param = args.txid_param;
|
|
600
809
|
result = await api('/api/affiliate-networks', { method: 'POST', body: JSON.stringify(body) });
|
|
601
810
|
break;
|
|
602
811
|
}
|
|
@@ -606,6 +815,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
606
815
|
if (typeof args.conversion_param === 'string') body.conversion_param = args.conversion_param;
|
|
607
816
|
if (typeof args.payout_param === 'string') body.payout_param = args.payout_param;
|
|
608
817
|
if (typeof args.currency_param === 'string') body.currency_param = args.currency_param;
|
|
818
|
+
if (typeof args.txid_param === 'string') body.txid_param = args.txid_param;
|
|
609
819
|
if (typeof args.archived === 'boolean') body.archived = args.archived;
|
|
610
820
|
result = await api(`/api/affiliate-networks/${Number(args.network_id)}`, { method: 'PATCH', body: JSON.stringify(body) });
|
|
611
821
|
break;
|
|
@@ -671,6 +881,61 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
671
881
|
break;
|
|
672
882
|
}
|
|
673
883
|
|
|
884
|
+
// ── Traffic sources ──
|
|
885
|
+
case 'list_traffic_sources': {
|
|
886
|
+
result = await api('/api/traffic-sources');
|
|
887
|
+
break;
|
|
888
|
+
}
|
|
889
|
+
case 'create_traffic_source': {
|
|
890
|
+
const body = { name: args.name };
|
|
891
|
+
if ('platform' in args) body.platform = args.platform;
|
|
892
|
+
if (Array.isArray(args.token_params)) body.token_params = args.token_params;
|
|
893
|
+
result = await api('/api/traffic-sources', { method: 'POST', body: JSON.stringify(body) });
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
case 'update_traffic_source': {
|
|
897
|
+
const body = {};
|
|
898
|
+
if (typeof args.name === 'string') body.name = args.name;
|
|
899
|
+
if ('platform' in args) body.platform = args.platform;
|
|
900
|
+
if (Array.isArray(args.token_params)) body.token_params = args.token_params;
|
|
901
|
+
if (typeof args.archived === 'boolean') body.archived = args.archived;
|
|
902
|
+
result = await api(`/api/traffic-sources/${Number(args.traffic_source_id)}`, { method: 'PATCH', body: JSON.stringify(body) });
|
|
903
|
+
break;
|
|
904
|
+
}
|
|
905
|
+
case 'delete_traffic_source': {
|
|
906
|
+
result = await api(`/api/traffic-sources/${Number(args.traffic_source_id)}`, { method: 'DELETE' });
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// ── Tracking pixels ──
|
|
911
|
+
case 'list_tracking_pixels': {
|
|
912
|
+
result = await api('/api/tracking-pixels');
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
case 'create_tracking_pixel': {
|
|
916
|
+
const body = { platform: args.platform, pixel_id: args.pixel_id };
|
|
917
|
+
if (typeof args.label === 'string') body.label = args.label;
|
|
918
|
+
if (typeof args.default_event === 'string') body.default_event = args.default_event;
|
|
919
|
+
if (typeof args.capi_token === 'string') body.capi_token = args.capi_token;
|
|
920
|
+
if (typeof args.active === 'boolean') body.active = args.active;
|
|
921
|
+
result = await api('/api/tracking-pixels', { method: 'POST', body: JSON.stringify(body) });
|
|
922
|
+
break;
|
|
923
|
+
}
|
|
924
|
+
case 'update_tracking_pixel': {
|
|
925
|
+
const body = {};
|
|
926
|
+
if (typeof args.pixel_id === 'string') body.pixel_id = args.pixel_id;
|
|
927
|
+
if (typeof args.label === 'string') body.label = args.label;
|
|
928
|
+
if (typeof args.default_event === 'string') body.default_event = args.default_event;
|
|
929
|
+
if ('capi_token' in args) body.capi_token = args.capi_token;
|
|
930
|
+
if (typeof args.active === 'boolean') body.active = args.active;
|
|
931
|
+
result = await api(`/api/tracking-pixels/${Number(args.pixel_id_row)}`, { method: 'PATCH', body: JSON.stringify(body) });
|
|
932
|
+
break;
|
|
933
|
+
}
|
|
934
|
+
case 'delete_tracking_pixel': {
|
|
935
|
+
result = await api(`/api/tracking-pixels/${Number(args.pixel_id_row)}`, { method: 'DELETE' });
|
|
936
|
+
break;
|
|
937
|
+
}
|
|
938
|
+
|
|
674
939
|
// ── Tracker: campaigns ──
|
|
675
940
|
case 'list_campaigns': {
|
|
676
941
|
result = await api('/api/campaigns');
|
|
@@ -678,11 +943,15 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
678
943
|
}
|
|
679
944
|
case 'create_campaign': {
|
|
680
945
|
const body = { name: args.name };
|
|
681
|
-
for (const k of ['traffic_source_label', 'country', 'cost_model', 'transition_mode']) {
|
|
946
|
+
for (const k of ['traffic_source', 'traffic_source_label', 'country', 'cost_model', 'transition_mode', 'conversion_event']) {
|
|
682
947
|
if (typeof args[k] === 'string') body[k] = args[k];
|
|
683
948
|
}
|
|
684
949
|
if (typeof args.cost_value === 'number') body.cost_value = args.cost_value;
|
|
685
950
|
if ('domain_id' in args) body.domain_id = args.domain_id;
|
|
951
|
+
if ('traffic_source_id' in args) body.traffic_source_id = args.traffic_source_id;
|
|
952
|
+
if ('conversion_pixel_id' in args) body.conversion_pixel_id = args.conversion_pixel_id;
|
|
953
|
+
if ('direct_slug' in args) body.direct_slug = args.direct_slug;
|
|
954
|
+
if ('fallback_url' in args) body.fallback_url = args.fallback_url;
|
|
686
955
|
if (Array.isArray(args.offer_ids)) body.offer_ids = args.offer_ids;
|
|
687
956
|
if (Array.isArray(args.offer_weights)) body.offer_weights = args.offer_weights;
|
|
688
957
|
if (Array.isArray(args.lander_ids)) body.lander_ids = args.lander_ids;
|
|
@@ -696,11 +965,16 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
696
965
|
}
|
|
697
966
|
case 'update_campaign': {
|
|
698
967
|
const body = {};
|
|
699
|
-
for (const k of ['name', 'traffic_source_label', 'country', 'cost_model', 'transition_mode']) {
|
|
968
|
+
for (const k of ['name', 'traffic_source_label', 'country', 'cost_model', 'transition_mode', 'conversion_event']) {
|
|
700
969
|
if (typeof args[k] === 'string') body[k] = args[k];
|
|
701
970
|
}
|
|
971
|
+
if ('traffic_source' in args) body.traffic_source = args.traffic_source;
|
|
972
|
+
if ('traffic_source_id' in args) body.traffic_source_id = args.traffic_source_id;
|
|
702
973
|
if (typeof args.cost_value === 'number') body.cost_value = args.cost_value;
|
|
703
974
|
if ('domain_id' in args) body.domain_id = args.domain_id;
|
|
975
|
+
if ('conversion_pixel_id' in args) body.conversion_pixel_id = args.conversion_pixel_id;
|
|
976
|
+
if ('direct_slug' in args) body.direct_slug = args.direct_slug;
|
|
977
|
+
if ('fallback_url' in args) body.fallback_url = args.fallback_url;
|
|
704
978
|
if (typeof args.archived === 'boolean') body.archived = args.archived;
|
|
705
979
|
result = await api(`/api/campaigns/${Number(args.campaign_id)}`, { method: 'PATCH', body: JSON.stringify(body) });
|
|
706
980
|
break;
|
|
@@ -709,6 +983,10 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
709
983
|
result = await api(`/api/campaigns/${Number(args.campaign_id)}`, { method: 'DELETE' });
|
|
710
984
|
break;
|
|
711
985
|
}
|
|
986
|
+
case 'duplicate_campaign': {
|
|
987
|
+
result = await api(`/api/campaigns/${Number(args.campaign_id)}/duplicate`, { method: 'POST' });
|
|
988
|
+
break;
|
|
989
|
+
}
|
|
712
990
|
case 'set_campaign_offers': {
|
|
713
991
|
result = await api(`/api/campaigns/${Number(args.campaign_id)}/offers`, {
|
|
714
992
|
method: 'PUT', body: JSON.stringify({ items: args.items }),
|
|
@@ -721,6 +999,16 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
721
999
|
});
|
|
722
1000
|
break;
|
|
723
1001
|
}
|
|
1002
|
+
case 'get_campaign_flow': {
|
|
1003
|
+
result = await api(`/api/campaigns/${Number(args.campaign_id)}/flow`);
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
case 'set_campaign_flow': {
|
|
1007
|
+
result = await api(`/api/campaigns/${Number(args.campaign_id)}/flow`, {
|
|
1008
|
+
method: 'PUT', body: JSON.stringify({ nodes: args.nodes, edges: args.edges }),
|
|
1009
|
+
});
|
|
1010
|
+
break;
|
|
1011
|
+
}
|
|
724
1012
|
case 'get_campaign_stats': {
|
|
725
1013
|
const days = args?.days ? `?days=${Number(args.days)}` : '';
|
|
726
1014
|
result = await api(`/api/campaigns/${Number(args.campaign_id)}/stats${days}`);
|
|
@@ -766,6 +1054,25 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
766
1054
|
result = await api(`/api/properties/${Number(args.property_id)}/stats${days}`);
|
|
767
1055
|
break;
|
|
768
1056
|
}
|
|
1057
|
+
case 'list_property_pages': {
|
|
1058
|
+
result = await api('/api/property-pages');
|
|
1059
|
+
break;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
case 'get_tracker_overview': {
|
|
1063
|
+
const days = args?.days ? `?days=${Number(args.days)}` : '';
|
|
1064
|
+
result = await api(`/api/tracker/overview${days}`);
|
|
1065
|
+
break;
|
|
1066
|
+
}
|
|
1067
|
+
case 'get_tracker_events': {
|
|
1068
|
+
const qs = new URLSearchParams();
|
|
1069
|
+
for (const k of ['days', 'q', 'stream', 'event', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'sort', 'dir', 'limit', 'offset']) {
|
|
1070
|
+
if (args?.[k] !== undefined && args[k] !== null && args[k] !== '') qs.set(k, String(args[k]));
|
|
1071
|
+
}
|
|
1072
|
+
const q = qs.toString();
|
|
1073
|
+
result = await api(`/api/tracker/events${q ? `?${q}` : ''}`);
|
|
1074
|
+
break;
|
|
1075
|
+
}
|
|
769
1076
|
|
|
770
1077
|
default:
|
|
771
1078
|
throw new Error(`unknown tool: ${name}`);
|