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 +7 -7
- package/mcp/tools.mjs +34 -3
- package/package.json +1 -1
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
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
2460
|
-
|
|
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.
|
|
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",
|