hermoso 0.1.202 → 0.1.203
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 +2 -2
- package/mcp/tools.mjs +108 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ scripts. Research the ads already winning in a market, generate finished image &
|
|
|
5
5
|
composited in, copy + CTA included), publish them to your own social channels, and build & manage the ad
|
|
6
6
|
campaigns behind them — all over [MCP](https://modelcontextprotocol.io) tools, a CLI, or installable Claude skills.
|
|
7
7
|
|
|
8
|
-
**
|
|
8
|
+
**789 tools.** `tools/list` is always the authoritative set; `hermoso_capabilities` (free) returns the live model
|
|
9
9
|
catalog with exact per-render credit costs plus the full capability map.
|
|
10
10
|
|
|
11
11
|
**What it connects to.** Ad platforms: Meta, Google Ads, TikTok Ads, LinkedIn Ads, Reddit Ads, X Ads,
|
|
@@ -171,7 +171,7 @@ block entirely if you signed in above; it is there for CI, where the process can
|
|
|
171
171
|
|
|
172
172
|
Then ask your agent: *“Generate an image ad with Hermoso.”*
|
|
173
173
|
|
|
174
|
-
### What the
|
|
174
|
+
### What the 789 tools cover
|
|
175
175
|
|
|
176
176
|
**Ad spy / research** — `find_competitors`, `competitor_teardown`, `pull_competitor_ads`, `research_ads`; the
|
|
177
177
|
Meta / Google / LinkedIn ad libraries (`search_meta_ads`, `search_google_ads`, `search_linkedin_ads`); organic
|
package/mcp/tools.mjs
CHANGED
|
@@ -6687,6 +6687,92 @@ function buildTools(rawServer, opts = {}, sink = null) {
|
|
|
6687
6687
|
const d = await apiPost('/api/meta/audience/delete', a);
|
|
6688
6688
|
return ok(d.note, d);
|
|
6689
6689
|
}));
|
|
6690
|
+
|
|
6691
|
+
// ── MESSENGER MARKETING MESSAGES (2026-09-05). Meta's paid re-engagement channel: messages OUTSIDE the 24-hour
|
|
6692
|
+
// window to people who opted in, billed by Meta to the brand's ad account per delivered message. Flow 2 of Meta's
|
|
6693
|
+
// onboarding (user token + marketing_messages_messenger) rides the existing Meta connection. App-Review gated:
|
|
6694
|
+
// until Meta grants the scope it works for app-role holders only, and list_messenger_subscribers says which.
|
|
6695
|
+
// Every endpoint, limit and error code: lib/messenger-marketing.mjs (read live 2026-09-05).
|
|
6696
|
+
server.registerTool('list_messenger_subscribers', {
|
|
6697
|
+
title: 'List Messenger marketing-message subscribers',
|
|
6698
|
+
description: 'The people who OPTED IN to marketing messages from a Facebook Page — their subscription tokens, the only address a paid Messenger marketing message can go to (there is no "message everyone"). Also reports whether this Meta connection carries the marketing_messages_messenger permission; a connection made before it was added must be reconnected. Free.',
|
|
6699
|
+
inputSchema: {
|
|
6700
|
+
pageId: z.string().optional().describe('Facebook Page id (from list_meta_pages); defaults to the brand’s one shared Page'),
|
|
6701
|
+
limit: z.number().optional().describe('1–1000, default 100'),
|
|
6702
|
+
customAudienceIds: z.string().optional().describe('comma-separated Messenger custom audience ids to filter by'),
|
|
6703
|
+
},
|
|
6704
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
6705
|
+
}, wrap(async (a) => {
|
|
6706
|
+
const d = await apiGet('/api/meta/marketing-messages/subscribers', a);
|
|
6707
|
+
const lines = (d.subscribers || []).slice(0, 50).map(r => `• ${r.token}${r.topic ? ` — “${r.topic}”` : ''}${r.status ? ` (${r.status})` : ''}${r.nextEligible ? `, next eligible ${r.nextEligible}` : ''}`);
|
|
6708
|
+
return ok(`${d.note}${lines.length ? `\n${lines.join('\n')}` : ''}${(d.subscribers || []).length > 50 ? `\n…and ${d.subscribers.length - 50} more` : ''}`, d);
|
|
6709
|
+
}));
|
|
6710
|
+
server.registerTool('create_messenger_marketing_campaign', {
|
|
6711
|
+
title: 'Create a Messenger marketing-message campaign',
|
|
6712
|
+
description: 'The container a paid Messenger marketing message is sent from (Meta act_<AD>/message_campaign). Budgets in USD; Meta bills per DELIVERED message against the campaign budget, and creating it sends and charges nothing. dailyBudgetUsd OR lifetimeBudgetUsd (omit both and Meta sets an estimated daily cap; Meta may spend up to 175% of a daily budget on one day, never more than 7× per week). REGION LAW: Meta lets us serve businesses in 20 countries only — US, Mexico, Brazil, India, Australia, Singapore, UAE, Saudi Arabia, Hong Kong, Taiwan, Thailand, Malaysia, Indonesia, Philippines, Vietnam, New Zealand, Chile, Colombia, Peru, Israel — NOT Canada, the EU or the UK — and cannot deliver to people in the EU, UK, Japan, South Korea or Australia. Political Pages are excluded. dryRun:true shows the exact body.',
|
|
6713
|
+
inputSchema: {
|
|
6714
|
+
adAccountId: z.string().describe('ad account id (act_… or digits)'),
|
|
6715
|
+
pageId: z.string().optional().describe('the sending Page; defaults to the brand’s one shared Page'),
|
|
6716
|
+
name: z.string(),
|
|
6717
|
+
dailyBudgetUsd: z.number().optional(),
|
|
6718
|
+
lifetimeBudgetUsd: z.number().optional(),
|
|
6719
|
+
pixelId: z.string().optional().describe('Meta Pixel for offsite conversion attribution'),
|
|
6720
|
+
startTime: z.string().optional().describe('ISO time, now or within 30 days'),
|
|
6721
|
+
endTime: z.string().optional(),
|
|
6722
|
+
dryRun: z.boolean().optional(),
|
|
6723
|
+
},
|
|
6724
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
6725
|
+
}, wrap(async (a) => { const d = await apiPost('/api/meta/marketing-messages/campaign', a); return ok(d.note, d); }));
|
|
6726
|
+
server.registerTool('list_messenger_marketing_campaigns', {
|
|
6727
|
+
title: 'List Messenger marketing-message campaigns',
|
|
6728
|
+
description: 'The marketing-message campaigns on an ad account: name, id, budgets, schedule. Free.',
|
|
6729
|
+
inputSchema: { adAccountId: z.string() },
|
|
6730
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
6731
|
+
}, wrap(async (a) => {
|
|
6732
|
+
const d = await apiGet('/api/meta/marketing-messages/campaigns', a);
|
|
6733
|
+
return ok(`${d.note}${(d.campaigns || []).length ? `\n${d.campaigns.map(c => `• ${c.name || '(unnamed)'} — ${c.campaignId}${c.dailyBudgetUsd != null ? `, $${c.dailyBudgetUsd}/day` : ''}${c.lifetimeBudgetUsd != null ? `, $${c.lifetimeBudgetUsd} lifetime` : ''}`).join('\n')}` : ''}`, d);
|
|
6734
|
+
}));
|
|
6735
|
+
server.registerTool('estimate_messenger_marketing_delivery', {
|
|
6736
|
+
title: 'Estimate Messenger marketing-message delivery and cost',
|
|
6737
|
+
description: 'Meta’s own estimate of how many marketing messages a budget would deliver from a Page and what it would cost, before anything is created. Free. Meta marks the metric “in development”, so treat it as a range.',
|
|
6738
|
+
inputSchema: { adAccountId: z.string(), pageId: z.string().optional(), dailyBudgetUsd: z.number().optional(), lifetimeBudgetUsd: z.number().optional(), lifetimeDays: z.number().optional() },
|
|
6739
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
6740
|
+
}, wrap(async (a) => { const d = await apiGet('/api/meta/marketing-messages/estimate', a); return ok(d.note, d); }));
|
|
6741
|
+
server.registerTool('send_messenger_marketing_message', {
|
|
6742
|
+
title: 'Send a paid Messenger marketing message',
|
|
6743
|
+
description: 'Send a PAID marketing message on Messenger to opted-in subscribers (Meta act_<AD>/messages). One message per subscriber per 12 hours — Meta’s rule, enforced before dispatch and by Meta. message.type: text | button (text + up to 3 web_url buttons) | generic (a card: title, subtitle, image, tap-through url, up to 3 buttons) | media (imageUrl or videoId + buttons). Give subscriptionTokens (≤200 per call, from list_messenger_subscribers) OR customAudienceId of a MESSENGER_SUBSCRIBER_LIST audience of 100+ people for a bulk send. Meta bills the ad account per delivered message; delivery, read and click events arrive on the Page webhook. dryRun:true previews the wire body and sends nothing. Meta’s frequency caps are silent: a refusal saying the person is capped is Meta protecting them, not a broken send.',
|
|
6744
|
+
inputSchema: {
|
|
6745
|
+
adAccountId: z.string(),
|
|
6746
|
+
campaignId: z.string().describe('from create_messenger_marketing_campaign'),
|
|
6747
|
+
subscriptionTokens: z.array(z.string()).optional(),
|
|
6748
|
+
customAudienceId: z.string().optional(),
|
|
6749
|
+
message: z.object({
|
|
6750
|
+
type: z.enum(['text', 'button', 'generic', 'media']),
|
|
6751
|
+
text: z.string().optional(), title: z.string().optional(), subtitle: z.string().optional(),
|
|
6752
|
+
imageUrl: z.string().optional(), videoId: z.string().optional(), url: z.string().optional().describe('card tap-through URL'),
|
|
6753
|
+
buttons: z.array(z.object({ title: z.string(), url: z.string() })).optional(),
|
|
6754
|
+
}),
|
|
6755
|
+
minConversationGapSeconds: z.number().optional().describe('hold the send if the person chatted with the Page within this many seconds'),
|
|
6756
|
+
dryRun: z.boolean().optional(),
|
|
6757
|
+
},
|
|
6758
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
6759
|
+
}, wrap(async (a) => {
|
|
6760
|
+
const d = await apiPost('/api/meta/marketing-messages/send', a);
|
|
6761
|
+
const bad = (d.results || []).filter(r => r.error);
|
|
6762
|
+
return ok(`${d.note}${bad.length ? `\nRefused: ${bad.slice(0, 10).map(r => `${r.token ? r.token.slice(0, 12) + '…' : 'audience'}: ${r.error}`).join('; ')}` : ''}`, d);
|
|
6763
|
+
}));
|
|
6764
|
+
server.registerTool('request_messenger_optin', {
|
|
6765
|
+
title: 'Ask a Messenger contact to subscribe to marketing messages',
|
|
6766
|
+
description: 'Send Meta’s opt-in request (the notification_messages template) to someone ALREADY in a Messenger conversation with the Page, inside the 24-hour window. If they accept, Meta posts a messaging_optin webhook with their subscription token and they appear in list_messenger_subscribers. One identical-title request per person per week. Free to send. The PSID comes from the Page inbox.',
|
|
6767
|
+
inputSchema: { pageId: z.string().optional(), psid: z.string().describe('the person’s Page-scoped id'), title: z.string().describe('what they are subscribing to, ≤65 characters'), imageUrl: z.string().optional(), timezone: z.string().optional().describe('their timezone, e.g. America/New_York'), dryRun: z.boolean().optional() },
|
|
6768
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
6769
|
+
}, wrap(async (a) => { const d = await apiPost('/api/meta/marketing-messages/optin', a); return ok(d.note, d); }));
|
|
6770
|
+
server.registerTool('delete_messenger_marketing_campaign', {
|
|
6771
|
+
title: 'Delete a Messenger marketing-message campaign',
|
|
6772
|
+
description: 'Delete a marketing-message campaign. Irreversible: the first call names it and refuses; confirm:true deletes.',
|
|
6773
|
+
inputSchema: { adAccountId: z.string(), campaignId: z.string(), confirm: z.boolean().optional() },
|
|
6774
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
6775
|
+
}, wrap(async (a) => { const d = await apiPost('/api/meta/marketing-messages/campaign/delete', a); return ok(d.note, d); }));
|
|
6690
6776
|
// ---------- Google Ads: read + manage (flagship, Meta-parity). Every spend change is confirm-gated. ----------
|
|
6691
6777
|
server.group('ads');
|
|
6692
6778
|
// GOOGLE MERCHANT CENTER (2026-08-07) — rides the google_ads connection (same grant, extra `content` scope).
|
|
@@ -17156,6 +17242,28 @@ function memoryNoteVerdict(text) {
|
|
|
17156
17242
|
return adsOut('ads', d.totalAds ?? raw.length, ads, '', 'linkedin');
|
|
17157
17243
|
}));
|
|
17158
17244
|
|
|
17245
|
+
// ── CREATOR SEARCH (2026-09-05): the creators already winning in a niche, folded out of the organic posts Ad Spy
|
|
17246
|
+
// already searches. Real people, scored on median views + engagement + consistency; no marketplace scope needed.
|
|
17247
|
+
server.registerTool('find_creators', {
|
|
17248
|
+
title: 'Find the creators already winning in a niche',
|
|
17249
|
+
description: 'Scan organic TikTok, Instagram Reels and YouTube for a niche across a few query variants, fold the posts into creators, and rank them on median views, engagement rate and how often they show up for that niche; the top rows get follower counts. Real people, not AI actors — for influencer sourcing, UGC casting and partnership prospecting ("who should we send product to?"). About one credit per search call (platforms × queries, default 3 × 3) plus one per enriched profile; repeats inside 20 minutes are free. Then shortlist (save_to_swipefile), check a profile (instagram_profile / fetch_social_data), draft outreach (generate_text), or approve them for Partnership Ads (manage_meta_partnership_creator).',
|
|
17250
|
+
inputSchema: {
|
|
17251
|
+
niche: z.string().describe('product category, topic or hashtag — "calorie tracker app", "matcha", "#cleanbeauty"'),
|
|
17252
|
+
platforms: z.array(z.enum(['tiktok', 'instagram', 'youtube'])).optional().describe('default all three'),
|
|
17253
|
+
limit: z.number().optional().describe('creators to return, 1–30 (default 12)'),
|
|
17254
|
+
queries: z.number().optional().describe('query variants per platform, 1–4 (default 3); each is a paid search call'),
|
|
17255
|
+
minAvgViews: z.number().optional(),
|
|
17256
|
+
minEngagement: z.number().optional().describe('interactions per view, 0–1 (0.05 = 5%)'),
|
|
17257
|
+
enrich: z.boolean().optional().describe('read follower counts for the top 6 (default true, ~1 credit each)'),
|
|
17258
|
+
},
|
|
17259
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true },
|
|
17260
|
+
}, wrap(async (a) => {
|
|
17261
|
+
const d = await apiPost('/api/creators/search', a);
|
|
17262
|
+
const fmt = (v) => v >= 1e6 ? `${(v / 1e6).toFixed(1)}M` : v >= 1e3 ? `${(v / 1e3).toFixed(v >= 1e5 ? 0 : 1)}K` : String(v);
|
|
17263
|
+
if (!(d.creators || []).length) return ok(`${d.summary} Nobody passed the floors — widen the niche, lower minAvgViews / minEngagement, or add platforms.`, d);
|
|
17264
|
+
const lines = d.creators.map(c => `• @${c.handle} (${c.platform})${c.name && c.name !== c.handle ? ` — ${c.name}` : ''}: ${c.posts} post${c.posts === 1 ? '' : 's'} in this niche, median ${fmt(c.medianPlays)} views, ${c.engagementRate == null ? 'engagement unknown' : `${(100 * c.engagementRate).toFixed(1)}% engagement`}${c.followers != null ? `, ${fmt(c.followers)} followers` : ''}, score ${c.score}${c.top?.link ? ` — top: ${c.top.link}` : ''}${c.profileUrl ? ` — ${c.profileUrl}` : ''}`);
|
|
17265
|
+
return ok(`${d.note}\n${lines.join('\n')}`, d);
|
|
17266
|
+
}));
|
|
17159
17267
|
server.registerTool('search_tiktok', {
|
|
17160
17268
|
_meta: openaiMeta(AD_SPY_URI, 'Searching TikTok videos…', 'Found TikTok videos'),
|
|
17161
17269
|
title: 'Search TikTok',
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.203",
|
|
4
4
|
"mcpName": "io.github.hermoso-ai/hermoso",
|
|
5
|
-
"description": "AI ad studio and marketing MCP server with
|
|
5
|
+
"description": "AI ad studio and marketing MCP server with 789 tools. Research the ads already running in any market, generate finished image, video and UGC avatar ads, publish and schedule them to your own channels, build and manage the ad campaigns behind them, and read what they achieved. AD PLATFORMS: Meta, Google Ads, TikTok Ads, LinkedIn Ads, Reddit Ads, X Ads, Pinterest Ads, Snapchat Ads, Microsoft Advertising, Apple Search Ads and ChatGPT Ads, plus product feeds in Google Merchant Center. PUBLISHING AND SCHEDULING: Facebook, Instagram, Threads, TikTok, YouTube, X, LinkedIn, Pinterest, Bluesky and Telegram. AD RESEARCH: the Meta, Google and LinkedIn ad libraries plus organic TikTok, Instagram, YouTube, Threads and Reddit. ANALYTICS: Google Analytics 4, Google Search Console and every connected platform's own post and campaign insights. Also brand onboarding, 50+ image and video generation models, ad scoring, competitor teardowns, Google Drive and OneDrive, a CLI and installable Claude skills.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"hermoso": "bin/hermoso.mjs"
|