hermoso 0.1.430 → 0.1.432

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.
@@ -1810,7 +1810,7 @@ export function libraryToolDefs() {
1810
1810
  campaignId: z.string().max(80).describe('the campaign id'),
1811
1811
  status: z.enum(['active', 'paused']).describe("'paused' stops it; 'active' turns it on and starts spending"),
1812
1812
  },
1813
- annotations: { title: 'Pause or turn on a campaign', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
1813
+ annotations: { title: 'Pause or turn on a campaign', readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
1814
1814
  _meta: { ...APP_ONLY },
1815
1815
  },
1816
1816
  };
package/mcp/tools.mjs CHANGED
@@ -2340,7 +2340,15 @@ export function parseToolScope(raw) {
2340
2340
  // read their descriptions as claiming what the tool cannot do (2026-10-18). x_post_metrics answers there.
2341
2341
  // fetch_social_data is a generic proxy over allowlisted vendor paths, which OpenAI's scan held as operations "not
2342
2342
  // individually exposed" (2026-10-18); see WIDGET_HOST_SPLITS for why it is withheld rather than split.
2343
- export const WITHHELD_FROM_WIDGET_HOSTS = new Set(['buy_credits', 'upgrade_plan', 'set_auto_reload', 'billing_status', 'delete_brand', 'connect_connector', 'x_post_insights', 'x_post_insights_historical', 'fetch_social_data']);
2343
+ // THE TOOLS THAT MOVE MONEY JOINED THEM 2026-10-19, for the plugin submission's attestation that the plugin "does not
2344
+ // initiate or execute money transfers, crypto transfers, or investment trades on behalf of users": a Stripe refund
2345
+ // pays money out, a Stripe subscription charges the customer's saved card, cancelling one ends a paid plan, and a
2346
+ // payment link exists to collect money. Ad budgets are NOT here: a budget is a spend setting on the user's own ad
2347
+ // account, not a transfer (an Apple budget order is a cap and spends nothing). The other Stripe writes (product, price,
2348
+ // coupon, customer) create records and move nothing; tools/chatgpt-no-money-movement-check.mjs makes a NEW Stripe
2349
+ // write fail until somebody classifies it.
2350
+ export const WIDGET_MONEY_MOVEMENT_TOOLS = Object.freeze(['refund_stripe_charge', 'create_stripe_subscription', 'cancel_stripe_subscription', 'create_stripe_payment_link']);
2351
+ export const WITHHELD_FROM_WIDGET_HOSTS = new Set(['buy_credits', 'upgrade_plan', 'set_auto_reload', 'billing_status', 'delete_brand', 'connect_connector', 'x_post_insights', 'x_post_insights_historical', 'fetch_social_data', ...WIDGET_MONEY_MOVEMENT_TOOLS]);
2344
2352
  // The withheld tools whose refusal is the generic-executor sentence rather than the commerce one.
2345
2353
  const WIDGET_GENERIC_PROXIES = new Set(['fetch_social_data']);
2346
2354
  const executorLike = (name) => EXECUTOR_TOOLS.has(name) || WIDGET_GENERIC_PROXIES.has(name);
@@ -5191,7 +5199,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
5191
5199
  hide: z.boolean().optional().describe('false to UNHIDE (default true)'),
5192
5200
  },
5193
5201
  outputSchema: { ok: z.boolean().optional(), hidden: z.boolean().optional() },
5194
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
5202
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
5195
5203
  }, wrap(async (a) => {
5196
5204
  const d = await apiPost('/api/threads/reply/hide', { replyId: a.replyId, hide: a.hide });
5197
5205
  return ok(`Reply ${a.replyId} ${d.hidden ? 'hidden' : 'unhidden'}.`, d);
@@ -9236,7 +9244,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
9236
9244
  instoreProductLocation: z.string().nullable().optional().describe('local only, max 20 bytes'),
9237
9245
  },
9238
9246
  outputSchema: { merchantCenterId: z.string().optional(), kind: z.string().optional(), key: z.string().optional(), productKey: z.string().optional(), replaced: z.boolean().optional(), preserved: z.array(z.string()).optional(), cleared: z.array(z.string()).optional(), attributes: z.any().optional(), warning: z.string().optional(), note: z.string().optional() },
9239
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
9247
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
9240
9248
  }, wrap(async (a) => {
9241
9249
  const d = await apiPost('/api/merchant/inventory', a);
9242
9250
  return ok(d.note, d);
@@ -9723,7 +9731,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
9723
9731
  loginCustomerId: z.string().optional().describe('manager id if operating through an MCC'),
9724
9732
  },
9725
9733
  outputSchema: { ok: z.boolean().optional(), campaignId: z.string().optional(), changed: z.boolean().optional(), networks: z.object({}).passthrough().optional(), note: z.string().optional() },
9726
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
9734
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
9727
9735
  }, wrap(async (a) => {
9728
9736
  const d = await apiPost('/api/google-ads/networks', a);
9729
9737
  return ok(d.note || `campaign ${a.campaignId} networks updated.`, d);
@@ -11498,7 +11506,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
11498
11506
  description: "Subscribe a Stripe customer (cus_…) to a price (price_…), optional quantity and trialDays. THIS CHARGES THE CUSTOMER'S SAVED PAYMENT METHOD when there is no trial, so it ALWAYS needs confirm:true (test keys included) after you show the user exactly what will be created. Read back; an INCOMPLETE status means no chargeable card is on file. Needs Stripe connected (Settings > Connectors > Stripe, or connect_connector).",
11499
11507
  inputSchema: { customerId: z.string(), priceId: z.string(), quantity: z.number().optional(), trialDays: z.number().optional(), metadata: z.record(z.any()).optional(), confirm: z.boolean().optional() },
11500
11508
  outputSchema: { ok: z.boolean().optional(), note: z.string().optional(), count: z.number().optional(), hasMore: z.boolean().optional() },
11501
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
11509
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
11502
11510
  }, wrap(async (a) => { const d = await apiPost('/api/stripe/subscription', a); return ok(d.note, d); }));
11503
11511
  server.registerTool('cancel_stripe_subscription', {
11504
11512
  title: 'cancel stripe subscription',
@@ -11512,7 +11520,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
11512
11520
  description: "Refund a Stripe charge (ch_… or a pi_… payment intent): the full refundable amount, or a partial `amount` in major units; optional reason duplicate / fraudulent / requested_by_customer. ALWAYS needs confirm:true, test keys included. Read back. Needs Stripe connected (Settings > Connectors > Stripe, or connect_connector).",
11513
11521
  inputSchema: { chargeId: z.string(), amount: z.number().optional(), reason: z.string().optional(), confirm: z.boolean().optional() },
11514
11522
  outputSchema: { ok: z.boolean().optional(), note: z.string().optional(), count: z.number().optional(), hasMore: z.boolean().optional() },
11515
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
11523
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
11516
11524
  }, wrap(async (a) => { const d = await apiPost('/api/stripe/refund', a); return ok(d.note, d); }));
11517
11525
  // ---------- HubSpot (2026-09-09): the brand's OWN CRM — contacts, an upsert by email, lead forms → contacts, deals ----------
11518
11526
  server.registerTool('list_hubspot_contacts', {
@@ -11923,7 +11931,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
11923
11931
  update: z.array(z.object({ criterionId: z.string(), bidAdjustment: z.number().describe('whole percent, -90 to 900') })).optional(),
11924
11932
  confirm: z.boolean().optional().describe('REQUIRED true when the campaign is ACTIVE'),
11925
11933
  },
11926
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
11934
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
11927
11935
  }, wrap(async (a) => { const d = await apiPost('/api/microsoft-ads/profile-targeting', a); return ok(d.note, d); }));
11928
11936
  server.registerTool('set_microsoft_ads_budget', {
11929
11937
  title: 'Set a Microsoft Advertising daily budget',
@@ -12196,7 +12204,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
12196
12204
  clickUrl: z.string().optional(), impressionUrl: z.string().optional(), confirm: z.boolean().optional().describe('required for a spend change on a LIVE campaign'),
12197
12205
  },
12198
12206
  outputSchema: { ...alOut, changed: z.array(z.string()).optional(), campaign: z.any().optional() },
12199
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
12207
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
12200
12208
  }, wrap(async (a) => { const d = await apiPost('/api/applovin-ads/campaign/update', a); return ok(d.note, d); }));
12201
12209
  server.registerTool('set_applovin_ads_status', {
12202
12210
  title: 'Turn an AppLovin campaign or creative set live or paused',
@@ -12220,7 +12228,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
12220
12228
  description: 'Create an AppLovin creative set from uploaded asset ids, born PAUSED, optionally inside a campaign. It must hold EITHER an HTML playable OR a portrait image (IMG_INTER_P) plus a video; Hermoso checks that against AppLovin’s own asset types before creating it.' + AL_CM_NOTE,
12221
12229
  inputSchema: { name: z.string(), assetIds: z.array(z.string()).describe('AppLovin asset ids'), campaignId: z.string().optional(), countries: alIds, languages: alIds.describe('e.g. ENGLISH'), url: z.string().optional().describe('landing page for this set only (WEB)') },
12222
12230
  outputSchema: { ...alOut, creativeSet: z.any().optional(), pausedAfterCreate: z.boolean().optional() },
12223
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
12231
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
12224
12232
  }, wrap(async (a) => { const d = await apiPost('/api/applovin-ads/creative-set', a); return ok(d.note, d); }));
12225
12233
  server.registerTool('remove_applovin_ads_creative_set', {
12226
12234
  title: 'Remove an AppLovin creative set from its campaigns',
@@ -12234,14 +12242,14 @@ function buildTools(rawServer, opts = {}, sink = null) {
12234
12242
  description: 'Edit an AppLovin creative set, read back: rename it, change its countries or languages (an empty list means all), its landing page (url; "" falls back to the campaign’s), replace its assets (assetIds, checked against AppLovin’s own asset types first), or add it to more campaigns (addToCampaignIds, up to 20). Adding a LIVE set to a LIVE campaign needs confirm:true. Status is set_applovin_ads_status.' + AL_CM_NOTE,
12235
12243
  inputSchema: { creativeSetId: z.string(), name: z.string().optional(), countries: alIds.describe('ISO codes; [] = all countries'), languages: alIds.describe('e.g. ENGLISH; [] = all'), url: z.string().optional().describe('landing page for this set; "" = the campaign’s'), assetIds: alIds.describe('REPLACES the set’s assets'), addToCampaignIds: alIds.describe('also run it in these campaigns (up to 20)'), confirm: z.boolean().optional().describe('required to add a LIVE set to a LIVE campaign') },
12236
12244
  outputSchema: { ...alOut, changed: z.array(z.string()).optional(), creativeSet: z.any().optional() },
12237
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
12245
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
12238
12246
  }, wrap(async (a) => { const d = await apiPost('/api/applovin-ads/creative-set/update', a); return ok(d.note, d); }));
12239
12247
  server.registerTool('clone_applovin_ads_creative_set', {
12240
12248
  title: 'Copy an AppLovin creative set into another campaign (paused)',
12241
12249
  description: 'Copy an AppLovin creative set into another campaign, born PAUSED and read back. AppLovin only clones a set that already belongs to a campaign.' + AL_CM_NOTE,
12242
12250
  inputSchema: { creativeSetId: z.string(), campaignId: z.string().describe('the campaign to copy it into') },
12243
12251
  outputSchema: { ...alOut, sourceId: z.string().optional(), campaignId: z.string().optional(), version: z.string().optional(), creativeSet: z.any().optional(), pausedAfterCreate: z.boolean().optional() },
12244
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
12252
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
12245
12253
  }, wrap(async (a) => { const d = await apiPost('/api/applovin-ads/creative-set/clone', a); return ok(d.note, d); }));
12246
12254
  server.registerTool('list_applovin_ads_catalogs', {
12247
12255
  title: 'List AppLovin catalogs',
@@ -12770,7 +12778,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
12770
12778
  budgetOrderIds: z.array(z.string()).optional().describe('budget order id(s) from list_apple_ads_budget_orders to assign this campaign to — a budget order is a spend CEILING SHARED across every campaign assigned to it, and it works ALONGSIDE dailyBudget rather than instead of it. Without this a budget order can never be drawn from.'),
12771
12779
  },
12772
12780
  outputSchema: { ok: z.boolean().optional(), level: z.string().optional(), id: z.string().optional(), verified: z.boolean().optional(), read: z.any().optional(), summary: z.string().optional(), note: z.string().optional() },
12773
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
12781
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
12774
12782
  }, wrap(async (a) => { const d = await apiPost('/api/apple-ads/update', a); return ok(`Apple Ads ${d.level} ${d.id} updated (verified: ${d.verified}):\n${d.summary}\n${d.note || ''}`, d); }));
12775
12783
 
12776
12784
  server.registerTool('set_apple_ads_status', {
@@ -13060,7 +13068,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
13060
13068
  confirm: z.boolean().optional().describe('REQUIRED true. Without it nothing changes and the refusal states the campaign and the exact budget or CPA change.'),
13061
13069
  },
13062
13070
  outputSchema: { ok: z.boolean().optional(), type: z.string().optional(), id: z.string().optional(), state: z.string().optional(), campaignId: z.string().nullable().optional(), armsSpend: z.boolean().optional(), verified: z.boolean().optional(), applied: z.any().optional(), read: z.any().optional(), summary: z.string().optional(), note: z.string().optional() },
13063
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
13071
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
13064
13072
  }, wrap(async (a) => { const d = await apiPost('/api/apple-ads/recommendation/apply', a); return ok(`Apple Ads ${d.type} recommendation ${d.id} — ${d.verified ? 'APPLIED' : 'NOT CONFIRMED'}:\n${d.summary}\n${d.note || ''}`, d); }));
13065
13073
 
13066
13074
  server.registerTool('dismiss_apple_ads_recommendation', {
@@ -13147,7 +13155,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
13147
13155
  invoiceName: z.string().optional(), orderNumber: z.string().optional(), clientName: z.string().optional(), primaryBuyerName: z.string().optional(),
13148
13156
  },
13149
13157
  outputSchema: { ok: z.boolean().optional(), level: z.string().optional(), id: z.string().optional(), verified: z.boolean().optional(), read: z.any().optional(), summary: z.string().optional(), note: z.string().optional() },
13150
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
13158
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
13151
13159
  }, wrap(async (a) => { const d = await apiPost('/api/apple-ads/budget-order', a); return ok(`Apple Ads budget order created:\n${d.summary}\n${d.note || ''}`, d); }));
13152
13160
 
13153
13161
  server.registerTool('update_apple_ads_budget_order', {
@@ -13166,7 +13174,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
13166
13174
  primaryBuyerName: z.string().optional(), primaryBuyerEmail: z.string().optional(), billingEmail: z.string().optional(),
13167
13175
  },
13168
13176
  outputSchema: { ok: z.boolean().optional(), level: z.string().optional(), id: z.string().optional(), verified: z.boolean().optional(), requested: z.any().optional(), read: z.any().optional(), summary: z.string().optional(), note: z.string().optional() },
13169
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
13177
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
13170
13178
  }, wrap(async (a) => { const d = await apiPost('/api/apple-ads/budget-order/update', a); return ok(`Apple Ads budget order ${d.id} updated (verified: ${d.verified}):\n${d.summary}\n${d.note || ''}`, d); }));
13171
13179
 
13172
13180
  server.registerTool('bulk_apple_ads_keywords', {
@@ -13181,7 +13189,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
13181
13189
  confirm: z.boolean().optional().describe('Required only when the target ad group or campaign is already ENABLED, where the new keywords could serve immediately.'),
13182
13190
  },
13183
13191
  outputSchema: { ok: z.boolean().optional(), kind: z.string().optional(), op: z.string().optional(), requested: z.number().optional(), succeeded: z.number().optional(), failed: z.number().optional(), verified: z.boolean().optional(), results: z.array(z.any()).optional(), summary: z.string().optional(), note: z.string().optional() },
13184
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
13192
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
13185
13193
  }, wrap(async (a) => { const d = await apiPost('/api/apple-ads/keywords/bulk', a); return ok(`${d.summary}\n${d.note || ''}`, d); }));
13186
13194
 
13187
13195
  server.registerTool('search_apple_ads_geo', {
@@ -13267,7 +13275,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
13267
13275
  purchaseOrderNumber: z.string().optional(),
13268
13276
  },
13269
13277
  outputSchema: { accountId: z.string().optional(), campaignId: z.string().optional(), updated: z.array(z.string()).optional(), changed: z.number().optional(), status: z.string().nullable().optional(), note: z.string().optional() },
13270
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
13278
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
13271
13279
  }, wrap(async (a) => { const d = await apiPost('/api/x-ads/campaign-update', a); return ok(d.note, d); }));
13272
13280
  server.registerTool('update_x_ads_line_item', {
13273
13281
  title: 'Change an X line item (ad group)',
@@ -13283,7 +13291,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
13283
13291
  totalBudget: z.number().optional().describe('only if the campaign is not budget-optimised'),
13284
13292
  },
13285
13293
  outputSchema: { accountId: z.string().optional(), lineItemId: z.string().optional(), updated: z.array(z.string()).optional(), changed: z.number().optional(), status: z.string().nullable().optional(), note: z.string().optional() },
13286
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
13294
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
13287
13295
  }, wrap(async (a) => { const d = await apiPost('/api/x-ads/line-item-update', a); return ok(d.note, d); }));
13288
13296
  server.registerTool('list_x_ads_promoted_tweets', {
13289
13297
  title: 'List X promoted posts',
@@ -13504,7 +13512,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
13504
13512
  expectedRevision: z.union([z.number(), z.string()]).optional().describe('REQUIRED for replace, optional for add/remove — membershipRevision from get_openai_ads_audience'),
13505
13513
  },
13506
13514
  outputSchema: { operationId: z.string().optional(), audienceId: z.string().optional(), operation: z.string().optional(), status: z.string().optional(), membersSent: z.number().optional(), rejected: z.number().optional(), note: z.string().optional() },
13507
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
13515
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
13508
13516
  }, wrap(async (a) => { const d = await apiPost('/api/openai-ads/audience/members', a); return ok(d.note, d); }));
13509
13517
  server.registerTool('list_openai_ads_audience_operations', {
13510
13518
  title: 'List ChatGPT Ads audience membership operations',
@@ -13653,7 +13661,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
13653
13661
  products: z.array(z.record(z.any())).describe('[{id, variants:[{id, title?, price?:{amount:int minor units, currency}, availability?:{available:bool}|{status:"in_stock"|"out_of_stock"}}]}]'),
13654
13662
  },
13655
13663
  outputSchema: { feedId: z.string().optional(), accepted: z.boolean().optional(), products: z.number().optional(), variants: z.number().optional(), note: z.string().optional() },
13656
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
13664
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
13657
13665
  }, wrap(async (a) => { const d = await apiPost('/api/openai-ads/feed-products', a); return ok(d.note, d); }));
13658
13666
  server.registerTool('update_openai_ads_object', {
13659
13667
  title: 'Edit a ChatGPT Ads campaign / ad group / ad',
@@ -13677,7 +13685,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
13677
13685
  confirm: z.boolean().optional().describe('REQUIRED true to change budget / bid / creative on a LIVE object'),
13678
13686
  },
13679
13687
  outputSchema: { ok: z.boolean().optional(), level: z.string().optional(), id: z.string().optional(), object: z.any().optional(), note: z.string().optional() },
13680
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
13688
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
13681
13689
  }, wrap(async (a) => {
13682
13690
  const d = await apiPost('/api/openai-ads/update', a);
13683
13691
  return ok(d.note, d);
@@ -13853,7 +13861,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
13853
13861
  }, wrap(async (a) => oaiNote(await apiPost('/api/openai-ads/lead-form/update', a))));
13854
13862
  server.registerTool('publish_openai_ads_lead_form', {
13855
13863
  title: 'Publish a ChatGPT Ads lead form', description: 'Publish the current draft of a ChatGPT Ads lead form so Business Agents can use it. expectedDraftRevisionId is the draftRevisionId you last read. Show the user the fields before publishing: the form is what real people will see.',
13856
- inputSchema: { leadFormId: z.string(), expectedDraftRevisionId: z.string() }, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
13864
+ inputSchema: { leadFormId: z.string(), expectedDraftRevisionId: z.string() }, annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
13857
13865
  }, wrap(async (a) => oaiNote(await apiPost('/api/openai-ads/lead-form/publish', a))));
13858
13866
  server.registerTool('archive_openai_ads_lead_form', {
13859
13867
  title: 'Archive a ChatGPT Ads lead form', description: 'Archive a ChatGPT Ads lead form. Not reversible through the API, and refused by ChatGPT Ads while a PUBLISHED Business Agent references it. confirm:true is required; without it nothing changes and the reply says what would.',
@@ -13890,7 +13898,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
13890
13898
  businessAgentId: z.string().optional().describe('replace THIS agent’s configuration instead of creating a new one'),
13891
13899
  name: z.string(), instructions: z.string(), description: z.string().optional(), privacyPolicyUrl: z.string().optional(),
13892
13900
  conversationStarters: z.array(z.string()).optional(), productFeedIds: z.array(z.string()).optional(), connectorIds: z.array(z.string()).optional(), toolIds: z.array(z.string()).optional(), leadFormId: z.string().optional(),
13893
- }, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
13901
+ }, annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
13894
13902
  }, wrap(async (a) => oaiNote(await apiPost('/api/openai-ads/business-agent', a))));
13895
13903
  server.registerTool('get_openai_ads_business_agent', {
13896
13904
  title: 'Read a ChatGPT Ads Business Agent', description: 'One Business Agent with its full configuration and publish state. Read-only, 0 credits.',
@@ -13902,7 +13910,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
13902
13910
  }, wrap(async (a) => oaiNote(await apiPost('/api/openai-ads/business-agent/preview', a))));
13903
13911
  server.registerTool('publish_openai_ads_business_agent', {
13904
13912
  title: 'Publish a ChatGPT Ads Business Agent', description: 'Publish a Business Agent’s draft so every campaign referencing it serves this version to real users. confirm:true required; show the user the instructions first.',
13905
- inputSchema: { businessAgentId: z.string(), confirm: z.boolean().optional() }, annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
13913
+ inputSchema: { businessAgentId: z.string(), confirm: z.boolean().optional() }, annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
13906
13914
  }, wrap(async (a) => oaiNote(await apiPost('/api/openai-ads/business-agent/publish', a))));
13907
13915
  server.registerTool('list_openai_ads_spend_windows', {
13908
13916
  title: 'List ChatGPT Ads account spending limits', description: 'Account-level SPENDING LIMITS on ChatGPT Ads, both kinds: the date-range SPEND-LIMIT WINDOWS (a ceiling on what the whole account may spend between an inclusive start date and an exclusive end date, with amount and spent so far) and the DAILY LIMIT (a per-day ceiling that renews at midnight in the account timezone, with what is spent and left today). Also returns the configuration `revision` that set_openai_ads_daily_spend_limit and remove_openai_ads_daily_spend_limit need as expectedRevision, and the earliest date a new daily limit may start. A limit caps spend; it never makes anything spend. OpenAI serves account spending limits only to ad accounts on postpaid INVOICE billing, and every spending-limit request, reads included, needs billing-management permission: a card-billed account is refused (a 403 that says only admins can do it) from any key, including one its own admin created. Read-only, 0 credits.',
@@ -13911,7 +13919,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
13911
13919
  server.registerTool('set_openai_ads_spend_window', {
13912
13920
  title: 'Create or edit a ChatGPT Ads spend-limit window', description: 'Create (no windowId) or edit (windowId) an account-level spend-limit window on ChatGPT Ads: startDate YYYY-MM-DD inclusive, endDate YYYY-MM-DD EXCLUSIVE (the window ends the day before), amount in the account currency (sent as micros), optional name and insertion-order id (ioId). A window is a CEILING on what the whole account may spend in that range; it never makes anything spend, and campaign budgets still apply underneath it. On edit, pass only the fields that change; a window whose canEdit is false (already started, per OpenAI) cannot be edited. Raising the amount lets campaigns spend up to their budgets, so show the user the old and new amounts. Reply carries the stored window (amount, spent so far, status). The live API serves this path since at least 2026-09-12 (it answered "Invalid URL" on 2026-09-03). It needs postpaid INVOICE billing and billing-management permission; a card-billed account is refused whatever key is used.',
13913
13921
  inputSchema: { windowId: z.string().optional(), startDate: z.string().optional(), endDate: z.string().optional(), amount: z.number().optional(), name: z.string().optional(), ioId: z.string().optional() },
13914
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
13922
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
13915
13923
  }, wrap(async (a) => oaiNote(await apiPost('/api/openai-ads/spend-window', a))));
13916
13924
  server.registerTool('delete_openai_ads_spend_window', {
13917
13925
  title: 'Delete a ChatGPT Ads spend-limit window', description: 'Delete an active or scheduled spend-limit window before it ends — this REMOVES a spending ceiling. confirm:true required.',
@@ -14128,7 +14136,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
14128
14136
  confirm: z.boolean().optional().describe('REQUIRED true to change bid / budget / campaign on a LIVE ad group'),
14129
14137
  },
14130
14138
  outputSchema: { ok: z.boolean().optional(), adAccountId: z.string().optional(), campaignId: z.string().optional(), adGroupId: z.string().optional(), changed: z.array(z.string()).optional(), adGroup: z.record(z.any()).optional(), note: z.string().optional() },
14131
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14139
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
14132
14140
  }, wrap(async (a) => { const d = await apiPost('/api/pinterest/ads-ad-group/update', a); return ok(d.note, d); }));
14133
14141
  server.registerTool('update_pinterest_ads_ad', {
14134
14142
  title: 'Edit a Pinterest ad',
@@ -14156,7 +14164,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
14156
14164
  pinId: z.string().optional().describe('DRAFT ads only — Pinterest refuses it on any other status'),
14157
14165
  },
14158
14166
  outputSchema: { ok: z.boolean().optional(), adAccountId: z.string().optional(), adGroupId: z.string().optional(), adId: z.string().optional(), changed: z.array(z.string()).optional(), reviewStatus: z.string().optional(), ad: z.record(z.any()).optional(), note: z.string().optional() },
14159
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14167
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
14160
14168
  }, wrap(async (a) => { const d = await apiPost('/api/pinterest/ads-ad/update', a); return ok(d.note, d); }));
14161
14169
  server.registerTool('set_pinterest_ads_budget', {
14162
14170
  title: 'Set a Pinterest campaign budget',
@@ -14379,7 +14387,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
14379
14387
  allowComments: z.boolean().describe('REQUIRED — Reddit demands allow_comments on every post update, and it is the only field it permits'),
14380
14388
  },
14381
14389
  outputSchema: { id: z.string().optional(), headline: z.string().optional(), allowComments: z.boolean().optional(), note: z.string().optional() },
14382
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14390
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
14383
14391
  }, wrap(async (a) => {
14384
14392
  const d = await apiPost('/api/reddit-ads/posts/update', a);
14385
14393
  return ok(d.note, d);
@@ -14475,7 +14483,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
14475
14483
  schedule: z.array(z.any()).optional(),
14476
14484
  },
14477
14485
  outputSchema: { id: z.string().optional(), name: z.string().optional(), status: z.string().optional(), budget: z.number().nullable().optional(), productSetId: z.string().nullable().optional(), shoppingType: z.string().nullable().optional(), note: z.string().optional() },
14478
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14486
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
14479
14487
  }, wrap(async (a) => {
14480
14488
  const d = await apiPost('/api/reddit-ads/ad-groups/update', a);
14481
14489
  return ok(d.note, d);
@@ -14568,7 +14576,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
14568
14576
  supplementaryText: z.string().optional().describe('up to 100 characters'),
14569
14577
  userGeneratedContent: z.enum(['OPT_IN', 'OPT_OUT']).optional(),
14570
14578
  },
14571
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
14579
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
14572
14580
  }, wrap(async (a) => { const d = await apiPost('/api/reddit-ads/max-template/update', a); return ok(d.note, d); }));
14573
14581
  server.registerTool('update_reddit_ads_ad', {
14574
14582
  title: 'Edit a Reddit ad',
@@ -14581,7 +14589,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
14581
14589
  clickUrl: z.string().optional().describe('pass an empty string to clear it'),
14582
14590
  },
14583
14591
  outputSchema: { id: z.string().optional(), name: z.string().optional(), status: z.string().optional(), note: z.string().optional() },
14584
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14592
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
14585
14593
  }, wrap(async (a) => {
14586
14594
  const d = await apiPost('/api/reddit-ads/ads/update', a);
14587
14595
  return ok(d.note, d);
@@ -14600,7 +14608,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
14600
14608
  endTime: z.string().optional(),
14601
14609
  },
14602
14610
  outputSchema: { id: z.string().optional(), name: z.string().optional(), status: z.string().optional(), note: z.string().optional() },
14603
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14611
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
14604
14612
  }, wrap(async (a) => {
14605
14613
  const d = await apiPost('/api/reddit-ads/campaign/update', a);
14606
14614
  return ok(d.note, d);
@@ -14814,7 +14822,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
14814
14822
  })).describe('up to 2,500 rows; every row must carry the same fields'),
14815
14823
  },
14816
14824
  outputSchema: { ok: z.boolean().optional(), customAudienceId: z.string().optional(), action: z.string().optional(), rows: z.number().optional(), sizeUpper: z.number().nullable().optional(), note: z.string().optional() },
14817
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
14825
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
14818
14826
  }, wrap(async (a) => {
14819
14827
  const d = await apiPost('/api/reddit-ads/audiences/users', a);
14820
14828
  return ok(d.note, d);
@@ -14867,7 +14875,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
14867
14875
  targeting: z.record(z.any()).optional().describe('REPLACES the existing targeting'),
14868
14876
  },
14869
14877
  outputSchema: { id: z.string().optional(), name: z.string().optional(), status: z.string().optional(), activeAdGroups: z.number().nullable().optional(), note: z.string().optional() },
14870
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
14878
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
14871
14879
  }, wrap(async (a) => {
14872
14880
  const d = await apiPost('/api/reddit-ads/saved-audiences/update', a);
14873
14881
  return ok(d.note, d);
@@ -15848,7 +15856,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
15848
15856
  leadSource: z.string().optional().describe('INSTANT_FORM (default) or DIRECT_MESSAGE'),
15849
15857
  },
15850
15858
  outputSchema: { action: z.string().optional(), advertiserId: z.string().optional(), leadSource: z.string().optional(), leadId: z.string().nullable().optional(), pageId: z.string().nullable().optional(), campaignId: z.string().nullable().optional(), adgroupId: z.string().nullable().optional(), adId: z.string().nullable().optional(), leadData: z.any().optional(), deleted: z.boolean().optional(), note: z.string().optional() },
15851
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
15859
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
15852
15860
  }, wrap(async (a) => {
15853
15861
  const d = await apiPost('/api/tiktok-ads/test-lead', a);
15854
15862
  return ok(`${d.action === 'delete' ? `Test lead ${d.leadId} deleted.` : d.leadId ? `Test lead ${d.leadId} on form ${d.pageId}: ${JSON.stringify(d.leadData)}` : 'No test lead exists on that form.'} ${d.note}`, d);
@@ -16244,7 +16252,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
16244
16252
  operation: z.enum(['HIDDEN', 'PUBLIC']).describe('HIDDEN takes them out of public view; PUBLIC puts them back'),
16245
16253
  },
16246
16254
  outputSchema: { advertiserId: z.string().optional(), commentIds: z.array(z.string()).optional(), operation: z.string().optional(), applied: z.boolean().optional(), note: z.string().optional() },
16247
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
16255
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
16248
16256
  }, wrap(async (a) => {
16249
16257
  const d = await apiPost('/api/tiktok-ads/comment-moderate', a);
16250
16258
  return ok(d.note, d);
@@ -16364,7 +16372,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
16364
16372
  confirm: z.boolean().optional().describe('REQUIRED true only to turn coverAllObjectives ON for the first time, because that is irreversible'),
16365
16373
  },
16366
16374
  outputSchema: { advertiserId: z.string().optional(), applied: z.boolean().optional(), before: z.any().optional(), after: z.any().optional(), changed: z.array(z.string()).optional(), note: z.string().optional() },
16367
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
16375
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
16368
16376
  }, wrap(async (a) => {
16369
16377
  const d = await apiPost('/api/tiktok-ads/brand-safety', a);
16370
16378
  return ok(d.note, d);
@@ -16477,7 +16485,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
16477
16485
  confirmScope: z.string().optional().describe('required in the same case — the scope token the refusal prints, computed from the version being sent'),
16478
16486
  },
16479
16487
  outputSchema: { advertiserId: z.string().optional(), ruleId: z.string().optional(), klass: z.string().optional(), canSpend: z.boolean().optional(), armingActions: z.array(z.string()).optional(), rule: z.any().optional(), verified: z.boolean().optional(), note: z.string().optional() },
16480
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
16488
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
16481
16489
  }, wrap(async (a) => {
16482
16490
  const d = await apiPost('/api/tiktok-ads/rule-update', a);
16483
16491
  return ok(d.note, d);
@@ -16888,7 +16896,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
16888
16896
  sendNotification: z.boolean().optional().describe('default false'),
16889
16897
  },
16890
16898
  outputSchema: { ttoAccountId: z.string().optional(), campaignId: z.string().optional(), handleNames: z.array(z.string()).optional(), advertiserIds: z.array(z.string()).optional(), added: z.any().optional(), note: z.string().optional() },
16891
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
16899
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
16892
16900
  }, wrap(async (a) => {
16893
16901
  const d = await apiPost('/api/tiktok-ads/tto-campaign-update', a);
16894
16902
  return ok(d.note, d);
@@ -17310,7 +17318,7 @@ function buildTools(rawServer, opts = {}, sink = null) {
17310
17318
  endTime: z.string().optional().describe('ISO 8601'),
17311
17319
  },
17312
17320
  outputSchema: { id: z.string().optional(), adAccountId: z.string().optional(), campaignId: z.string().optional(), name: z.string().optional(), status: z.string().optional(), optimizationGoal: z.string().optional(), dailyBudget: z.number().nullable().optional(), dailyBudgetMicro: z.number().nullable().optional(), currency: z.string().nullable().optional(), verified: z.boolean().optional(), note: z.string().optional() },
17313
- annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
17321
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
17314
17322
  }, wrap(async (a) => {
17315
17323
  const d = await apiPost('/api/snapchat-ads/adsquad', a);
17316
17324
  const money = d.dailyBudget != null ? ` Daily budget ${d.dailyBudget} ${d.currency || ''} (${d.dailyBudgetMicro} micro).` : '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hermoso",
3
- "version": "0.1.430",
3
+ "version": "0.1.432",
4
4
  "mcpName": "io.github.hermoso-ai/hermoso",
5
5
  "description": "AI marketing agent and AI ad generator, run from your own AI agent. 922 tools. Publishing, scheduling, ad campaign management, comments, DMs and analytics cost no credits on every plan; credits are only for generating creative and for Ad Spy research. AD PLATFORMS: Meta, Google Ads, TikTok Ads, LinkedIn Ads, Reddit Ads, X Ads, Pinterest Ads, Snapchat Ads, Microsoft Advertising, Apple Search Ads, ChatGPT Ads and AppLovin Ads, plus product feeds in Google Merchant Center. PUBLISHING AND SCHEDULING: Facebook, Instagram, Threads, TikTok, YouTube, X, LinkedIn, Pinterest, Bluesky, Telegram and Google Business Profile. INSTAGRAM DM AUTOMATIONS: a keyword comment, a DM, a story reply or an ig.me link gets an automatic private reply from the brand. 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",