hermoso 0.1.259 → 0.1.261

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/http.mjs CHANGED
@@ -243,12 +243,12 @@ const inflightNameOf = (body) => { const msgs = Array.isArray(body) ? body : [bo
243
243
  // `?tools=research,create` narrows the roster this connection advertises (see registerTools). Read here rather
244
244
  // than inside registerTools so BOTH the anonymous discovery handshake and a real session honour the same query,
245
245
  // and so an unknown group is refused at the door with the valid list instead of silently serving every group.
246
- // ABSENT, an authenticated session resolves to `defaultToolGroups` in tools.mjs, which is the FULL roster unless
247
- // this process sets MCP_CORE_FIRST=1 — and production does not set it (measured 2026-09-20 off the Cloud Run
248
- // service env). This comment used to say an authenticated session "resolves to the CORE-FIRST default" in one
249
- // sentence and "the default is the full roster" in the next; the second one is the true one. When core-first IS
250
- // on, the list is the core tools plus a few that make the connection drivable, with everything else held out of
251
- // the LIST on size and reachable through find_tools + call_tool. `?tools=all` restores the full roster for one
246
+ // ABSENT, an authenticated session resolves to `defaultToolGroups` in tools.mjs: the FULL roster unless this
247
+ // process sets MCP_CORE_FIRST=1. PRODUCTION SETS IT (since 2026-09-20, read off the Cloud Run service env; for the
248
+ // three weeks before, this comment claimed core-first was the default while the variable was absent). With it on,
249
+ // the list is the core tools, the few that make the connection drivable and the headline verb of each area
250
+ // (CORE_FIRST_HEADLINE), with everything else held out of the LIST on size and reachable through find_tools +
251
+ // call_tool. `?tools=all` restores the full roster for one
252
252
  // connection. The anonymous discovery path above is
253
253
  // deliberately NOT core-first — see the comment on its registerTools call.
254
254
  // The scope fixed here is the STARTING roster, not a cage: `enable_tools` widens it mid-session and the SDK
@@ -355,7 +355,7 @@ const inflightNameOf = (body) => { const msgs = Array.isArray(body) ? body : [bo
355
355
  // connectedProviders() ([[failed-read-is-not-empty]]).
356
356
  const connectors = await mcpCtx.run({ token, remote: true, client: rememberedClient(req) }, () => connectedProviders());
357
357
  const server = new McpServer({ name: 'hermoso', version: '1.0.0' }, { instructions: MCP_INSTRUCTIONS });
358
- registerTools(server, { only: scope.groups, directory: scope.directory || false, connectors, widgetHost: isWidgetHost(entry?.client || clientInfoOf(req.body), req) , hosted: true }); // the SAME tools as stdio (minus any the caller scoped out) — and every /api call they make carries this user's token
358
+ registerTools(server, { only: scope.groups, directory: scope.directory || false, connectors, widgetHost: isWidgetHost(entry?.client || clientInfoOf(req.body), req) , hosted: true, client: entry?.client || rememberedClient(req) }); // the SAME tools as stdio (minus any the caller scoped out) — and every /api call they make carries this user's token
359
359
  const transport = new StreamableHTTPServerTransport({
360
360
  // CSPRNG, per the spec's SHOULD for session ids (Math.random() is not one).
361
361
  sessionIdGenerator: () => 'sess_' + randomUUID().replace(/-/g, ''),
package/mcp/tools.mjs CHANGED
@@ -1936,7 +1936,30 @@ export const coreFirstRoster = (env = process.env) => CORE_FIRST_ENV.some((k) =>
1936
1936
  // every existing caller and every per-group count the site and the docs derive. tools/core-first-roster-check.mjs
1937
1937
  // asserts each name is registered and that NONE of them is connector-gated (one that were would be listed and
1938
1938
  // then answer 401, which is the thing the connector gate exists to prevent).
1939
- export const CORE_FIRST_EXTRA = Object.freeze(['get_brand', 'get_job', 'list_jobs', 'list_connectors', 'list_scheduled']);
1939
+ // THE SHORT LIST HAS TO LOOK LIKE THE PRODUCT (2026-09-20, Dave: "we dont want to degrade quality or make it look like
1940
+ // there's less capability"). The first core-first list was the `core` group plus the five above, and read live off
1941
+ // production it was: enable_tools, find_tools, call_tool, hermoso_capabilities, hermoso_credits, buy_credits,
1942
+ // report_bug, request_feature, billing_status, upgrade_plan, set_auto_reload, list_brands, use_brand, create_brand,
1943
+ // delete_brand … — a billing utility. Not one listed tool made an ad, researched a competitor or published a post, so
1944
+ // a person scanning the connector in Claude, ChatGPT or Cursor saw no marketing product, and a client that does not
1945
+ // reliably search (`find_tools` is an instruction, and weaker hosts skip instructions) had no way to discover one.
1946
+ // So the HEADLINE VERBS of each area ride in the list: research, create, the variant tools, publish, measure. Every
1947
+ // name here is un-gated (listed with nothing connected and never answering 401), and everything else is still one
1948
+ // find_tools + call_tool away. The weight this costs is measured in tools/roster-weight-check.mjs.
1949
+ export const CORE_FIRST_HEADLINE = Object.freeze([
1950
+ // research
1951
+ 'find_competitors', 'pull_competitor_ads', 'competitor_teardown', 'research_ads', 'mine_angles',
1952
+ // create
1953
+ 'draft_brand', 'plan_ad', 'render_ad', 'generate_image', 'generate_video', 'clone_video', 'clone_static', 'make_template_ad',
1954
+ // variants of a finished ad
1955
+ 'edit_image', 'headline_variants', 'hook_variants', 'multiply_ad',
1956
+ // publish, files, measure
1957
+ 'schedule_post', 'list_library', 'upload_file', 'post_performance', 'analyze_campaigns',
1958
+ ]);
1959
+ export const CORE_FIRST_EXTRA = Object.freeze(['get_brand', 'get_job', 'list_jobs', 'list_connectors', 'list_scheduled', ...CORE_FIRST_HEADLINE]);
1960
+ // Hosts that have been SEEN to follow find_tools → call_tool. A name that does not match keeps the full roster.
1961
+ export const CORE_FIRST_VERIFIED_HOST_RE = /claude|chatgpt|openai-mcp/i;
1962
+ export const hostTakesCoreFirst = (client) => CORE_FIRST_VERIFIED_HOST_RE.test(String(client || ''));
1940
1963
  export function defaultToolGroups(env = process.env) { return coreFirstRoster(env) ? ['core'] : [...DEFAULT_TOOL_GROUPS]; }
1941
1964
 
1942
1965
  // Parse a `tools=` scope. Returns {groups} or {error} — an unknown name is REFUSED BY NAME rather than dropped,
@@ -2456,8 +2479,16 @@ function newToolScope(opts) {
2456
2479
  // AN EXPLICIT SCOPE ALWAYS WINS. `only` is what `?tools=`, `HERMOSO_TOOLS` and `/v1` pass; core-first is only
2457
2480
  // what an UNSTATED default resolves to, so a caller who named their groups gets exactly those and nothing here
2458
2481
  // narrows them. `coreFirst` is therefore false for every explicit scope, including `?tools=all`.
2459
- const coreFirst = !opts.only && coreFirstRoster();
2460
- const asked = opts.only ? new Set(opts.only) : new Set(defaultToolGroups());
2482
+ // …AND ONLY A HOST WE HAVE SEEN SEARCH GETS THE SHORT LIST (2026-09-20, Dave, asked twice: "will they all be able to
2483
+ // search the other tools and understand that more is available? we dont want to degrade quality or make it seem like
2484
+ // we have less functionality"). The honest answer was "not provably": `find_tools` is an instruction, the ledger keeps
2485
+ // failures and not successful calls, so which hosts follow it could not be read from history. What HAS been seen:
2486
+ // Claude (claude.ai and Claude Code) running find_tools → call_tool on a roster that lacked the tool, and ChatGPT,
2487
+ // whose connector was tested on exactly that route. Cursor, Codex and anything unnamed are unverified, so on the
2488
+ // HOSTED transport they keep the full default roster and nothing can look smaller than it is. The stdio CLI has no
2489
+ // host to ask and stays a plain env opt-in. Widen CORE_FIRST_VERIFIED_HOST_RE on evidence, never on a guess.
2490
+ const coreFirst = !opts.only && coreFirstRoster() && (!opts.hosted || hostTakesCoreFirst(opts.client));
2491
+ const asked = opts.only ? new Set(opts.only) : new Set(coreFirst ? ['core'] : [...DEFAULT_TOOL_GROUPS]);
2461
2492
  asked.add('core'); // discovery/credits/billing/jobs must exist in EVERY roster or the connection is unusable
2462
2493
  // `connectors` is `{connected:Set<provider>, readOk:boolean}` from the transport's own free read, or absent.
2463
2494
  // ABSENT AND `readOk:false` BEHAVE IDENTICALLY, and that is the fail-open law rather than a convenience:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hermoso",
3
- "version": "0.1.259",
3
+ "version": "0.1.261",
4
4
  "mcpName": "io.github.hermoso-ai/hermoso",
5
5
  "description": "Marketing on autopilot, run from your own AI agent. 847 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 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",