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.
- package/mcp/openai-extensions.mjs +1 -1
- package/mcp/tools.mjs +48 -40
- package/package.json +1 -1
|
@@ -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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
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",
|