@buygolinks/mcp 0.17.1 → 0.20.0

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 CHANGED
@@ -64,6 +64,7 @@ Most MCP clients use the same shape. Point them at the `npx -y @buygolinks/mcp`
64
64
  |---|---|
65
65
  | `create_link` | Create a short link. The destination URL is passed through untouched (include your own affiliate tag). |
66
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
+ | `refresh_link_image` | Re-attempt fetching a link's product/preview image (Amazon PDP scrape for `/dp/` links, Open Graph otherwise). Use when a link has no thumbnail or a stale one. Returns `{ found: false }` if nothing could be fetched this time. |
67
68
  | `list_links` | List your links with click counts. |
68
69
  | `get_link_stats` | Per-link analytics (by day, country, device, browser, hour, weekday). |
69
70
  | `get_overview_stats` | Account-wide analytics. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buygolinks/mcp",
3
- "version": "0.17.1",
3
+ "version": "0.20.0",
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
@@ -91,6 +91,18 @@ const TOOLS = [
91
91
  required: ['link_id'],
92
92
  },
93
93
  },
94
+ {
95
+ name: 'refresh_link_image',
96
+ description:
97
+ 'Re-attempt fetching the product/preview image for an existing short link. Re-runs the same best-effort scrape used at link creation — the Amazon product-image scraper for Amazon /dp/ links, or a generic Open Graph (og:image / twitter:image) scrape for any other URL — and rehosts the result on buygolinks storage. Use this when a link was created without an image (the thumbnail shows a letter/placeholder) or to update a stale image. Returns { found: true, link } with the updated `product_image_url` on success, or { found: false } if no image could be fetched this time (the existing image, if any, is left untouched — try again later).',
98
+ inputSchema: {
99
+ type: 'object',
100
+ properties: {
101
+ link_id: { type: 'number', description: 'The numeric id of the link (from list_links / create_link).' },
102
+ },
103
+ required: ['link_id'],
104
+ },
105
+ },
94
106
  {
95
107
  name: 'list_links',
96
108
  description:
@@ -446,6 +458,10 @@ const TOOLS = [
446
458
  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
459
  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
460
  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.' },
461
+ report_value_mode: { type: 'string', enum: ['payout', 'fixed', 'adjusted'], description: 'What revenue VALUE to report back to the ad platform on a CAPI conversion. "payout" (default) = the real conversion payout; "fixed" = always report report_value_fixed (e.g. 1); "adjusted" = real payout minus report_value_adjust_pct % then minus report_value_adjust_flat (so the user\'s margin/cost is withheld from what the platform optimizes against). Floored at 0.' },
462
+ report_value_fixed: { type: ['number', 'null'], description: 'Used when report_value_mode = "fixed": the exact value (USD) reported on every conversion regardless of real payout.' },
463
+ report_value_adjust_pct: { type: ['number', 'null'], description: 'Used when report_value_mode = "adjusted": percentage (0–100) subtracted from the real payout first (e.g. 10 reports payout − 10%).' },
464
+ report_value_adjust_flat: { type: ['number', 'null'], description: 'Used when report_value_mode = "adjusted": flat amount (USD) subtracted after the percentage.' },
449
465
  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
466
  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.' },
451
467
  offer_ids: { type: 'array', items: { type: 'number' } },
@@ -479,6 +495,10 @@ const TOOLS = [
479
495
  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
496
  conversion_pixel_id: { type: ['number', 'null'], description: 'Id of a tracking pixel to fire on conversion, or null to clear.' },
481
497
  conversion_event: { type: 'string', description: 'Event the conversion pixel fires (must be valid for that pixel\'s platform).' },
498
+ report_value_mode: { type: 'string', enum: ['payout', 'fixed', 'adjusted'], description: 'What revenue value to report back to the ad platform on conversion: "payout" (real), "fixed" (report_value_fixed), or "adjusted" (real payout − report_value_adjust_pct% − report_value_adjust_flat, floored at 0).' },
499
+ report_value_fixed: { type: ['number', 'null'], description: 'Fixed value (USD) reported on every conversion when report_value_mode = "fixed". Null to clear.' },
500
+ report_value_adjust_pct: { type: ['number', 'null'], description: 'Percentage (0–100) subtracted from real payout when report_value_mode = "adjusted". Null to clear.' },
501
+ report_value_adjust_flat: { type: ['number', 'null'], description: 'Flat USD amount subtracted (after the %) when report_value_mode = "adjusted". Null to clear.' },
482
502
  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
503
  fallback_url: { type: ['string', 'null'], description: 'Never-dead-end fallback URL when no live offer exists, or null to use the platform default.' },
484
504
  archived: { type: 'boolean' },
@@ -680,6 +700,12 @@ const TOOLS = [
680
700
  '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
701
  inputSchema: { type: 'object', properties: {} },
682
702
  },
703
+ {
704
+ name: 'list_observed_campaign_tags',
705
+ description:
706
+ 'List the distinct `utm_campaign` values that have actually been SEEN arriving on the user\'s properties/visits over the last 90 days, most-active first. Each entry is `{ utm_campaign, events, utm_source, utm_medium, lander_url, claimed_by }` where `claimed_by` is `{ id, name }` of the BGL campaign whose `direct_slug` already owns that tag, or null if unclaimed. Use this to WIRE UP AN ALREADY-RUNNING DIRECT-LINK CAMPAIGN: when an ad on FB/Google points straight at a lander (no tracker hop) with `?utm_campaign={tag}`, that tag shows up here once the universal property tag reports a page view. Pick an unclaimed tag and set it as a campaign\'s `direct_slug` (via `update_campaign`) so BGL attributes that traffic — and its `/click` CTA — to the right campaign + offer. Skip tags whose `claimed_by` is non-null (another campaign already owns them).',
707
+ inputSchema: { type: 'object', properties: {} },
708
+ },
683
709
  {
684
710
  name: 'get_tracker_overview',
685
711
  description:
@@ -760,6 +786,12 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
760
786
  });
761
787
  break;
762
788
  }
789
+ case 'refresh_link_image': {
790
+ result = await api(`/api/links/${Number(args.link_id)}/refresh-image`, {
791
+ method: 'POST',
792
+ });
793
+ break;
794
+ }
763
795
  case 'list_links': {
764
796
  const qs = args?.include_archived ? '?include_archived=1' : '';
765
797
  result = await api(`/api/links${qs}`);
@@ -950,6 +982,10 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
950
982
  if ('domain_id' in args) body.domain_id = args.domain_id;
951
983
  if ('traffic_source_id' in args) body.traffic_source_id = args.traffic_source_id;
952
984
  if ('conversion_pixel_id' in args) body.conversion_pixel_id = args.conversion_pixel_id;
985
+ if (typeof args.report_value_mode === 'string') body.report_value_mode = args.report_value_mode;
986
+ if ('report_value_fixed' in args) body.report_value_fixed = args.report_value_fixed;
987
+ if ('report_value_adjust_pct' in args) body.report_value_adjust_pct = args.report_value_adjust_pct;
988
+ if ('report_value_adjust_flat' in args) body.report_value_adjust_flat = args.report_value_adjust_flat;
953
989
  if ('direct_slug' in args) body.direct_slug = args.direct_slug;
954
990
  if ('fallback_url' in args) body.fallback_url = args.fallback_url;
955
991
  if (Array.isArray(args.offer_ids)) body.offer_ids = args.offer_ids;
@@ -973,6 +1009,10 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
973
1009
  if (typeof args.cost_value === 'number') body.cost_value = args.cost_value;
974
1010
  if ('domain_id' in args) body.domain_id = args.domain_id;
975
1011
  if ('conversion_pixel_id' in args) body.conversion_pixel_id = args.conversion_pixel_id;
1012
+ if (typeof args.report_value_mode === 'string') body.report_value_mode = args.report_value_mode;
1013
+ if ('report_value_fixed' in args) body.report_value_fixed = args.report_value_fixed;
1014
+ if ('report_value_adjust_pct' in args) body.report_value_adjust_pct = args.report_value_adjust_pct;
1015
+ if ('report_value_adjust_flat' in args) body.report_value_adjust_flat = args.report_value_adjust_flat;
976
1016
  if ('direct_slug' in args) body.direct_slug = args.direct_slug;
977
1017
  if ('fallback_url' in args) body.fallback_url = args.fallback_url;
978
1018
  if (typeof args.archived === 'boolean') body.archived = args.archived;
@@ -1058,6 +1098,10 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
1058
1098
  result = await api('/api/property-pages');
1059
1099
  break;
1060
1100
  }
1101
+ case 'list_observed_campaign_tags': {
1102
+ result = await api('/api/observed-campaign-tags');
1103
+ break;
1104
+ }
1061
1105
 
1062
1106
  case 'get_tracker_overview': {
1063
1107
  const days = args?.days ? `?days=${Number(args.days)}` : '';