hermoso 0.1.90 → 0.1.112

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@ scripts. Research the ads already winning in a market, generate finished image &
5
5
  composited in, copy + CTA included), publish them to your own social channels, and build & manage the ad
6
6
  campaigns behind them — all over [MCP](https://modelcontextprotocol.io) tools, a CLI, or installable Claude skills.
7
7
 
8
- **427 tools.** `tools/list` is always the authoritative set; `hermoso_capabilities` (free) returns the live model
8
+ **519 tools.** `tools/list` is always the authoritative set; `hermoso_capabilities` (free) returns the live model
9
9
  catalog with exact per-render credit costs plus the full capability map.
10
10
 
11
11
  **It is not all-or-nothing.** Research, creation, publishing/scheduling and ads management are four *independent*
@@ -53,7 +53,7 @@ Cursor / Codex — add to `mcp.json` (Codex uses the TOML equivalent):
53
53
 
54
54
  Then ask your agent: *“Generate an image ad with Hermoso.”*
55
55
 
56
- ### What the 427 tools cover
56
+ ### What the 519 tools cover
57
57
 
58
58
  **Ad spy / research** — `find_competitors`, `competitor_teardown`, `pull_competitor_ads`, `research_ads`; the
59
59
  Meta / Google / LinkedIn ad libraries (`search_meta_ads`, `search_google_ads`, `search_linkedin_ads`); organic
@@ -2,8 +2,8 @@
2
2
  // Hermoso MCP server (stdio transport) — lets Claude Code / Cursor / Codex (and any stdio MCP client) drive Hermoso:
3
3
  // research competitors, plan ads, and generate images/videos/avatars, all against the running Hermoso server.
4
4
  //
5
- // Local (today): node mcp/hermoso-mcp.mjs # talks to https://app.hermoso.ai (HERMOSO_API_BASE to override, e.g. http://localhost:3000 when self-running)
6
- // Auth (today): none — the local server resolves the dev account. Set HERMOSO_TOKEN once real auth lands.
5
+ // Local (today): node mcp/hermoso-mcp.mjs # talks to http://localhost:3000 (HEIST_API_BASE to override)
6
+ // Auth (today): none — the local server resolves the dev account. Set HEIST_TOKEN once real auth lands.
7
7
  //
8
8
  // stdout is the JSON-RPC channel — NEVER print to it. All logging goes to stderr (console.error).
9
9
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
@@ -17,11 +17,15 @@ const server = new McpServer({ name: 'hermoso-mcp', version: '1.0.0' }, {
17
17
  instructions: MCP_INSTRUCTIONS,
18
18
  });
19
19
 
20
- // Optional roster scoping, same groups as the hosted connector's ?tools= (see registerTools). A client that
21
- // loads every tool definition eagerly spends ~154k tokens on the full roster; HERMOSO_TOOLS=channels,ads narrows it.
20
+ // Roster scoping, same groups as the hosted connector's ?tools= (see registerTools). The DEFAULT is every group
21
+ // except `ads` 238 of the 436 tools and ~66% of the schema weight which takes an eagerly-loading client from
22
+ // ~235k tokens to ~83k. Nothing is lost: `enable_tools` switches a group on mid-session with no reconnect.
23
+ // HERMOSO_TOOLS=all restores the full roster; HERMOSO_TOOLS=create,channels narrows it further.
22
24
  // An unknown group EXITS rather than silently serving all of them — a scoped connection you did not get is
23
25
  // worse than one you were told you could not have.
24
- const _scope = parseToolScope(process.env.HERMOSO_TOOLS);
26
+ // Both env names are read: HERMOSO_TOOLS is the current prefix, HEIST_TOOLS the pre-rebrand one that is live in
27
+ // people's configs today. Renaming a variable someone already set is how a working setup goes quiet.
28
+ const _scope = parseToolScope(process.env.HERMOSO_TOOLS || process.env.HEIST_TOOLS);
25
29
  if (_scope.error) { console.error(`[hermoso-mcp] ${_scope.error}`); process.exit(1); }
26
30
  registerTools(server, { only: _scope.groups });
27
31
 
package/mcp/http.mjs CHANGED
@@ -5,11 +5,11 @@
5
5
  //
6
6
  // It is written so the cloud step is a CONFIG FLIP, not a rewrite — but it is intentionally OFF and will REFUSE
7
7
  // to mount until BOTH are true:
8
- // (1) HERMOSO_MCP_REMOTE=1, and
8
+ // (1) HEIST_MCP_REMOTE=1, and
9
9
  // (2) a real token verifier is wired (verifyBearer) — i.e. Firebase Auth (or equivalent) is configured.
10
10
  // Why it must stay off locally: a public money-spending endpoint cannot exist without authenticated identity
11
11
  // (the no-anon-spend rule), there is no hosted origin yet, and per the rollout plan cloud is provisioned
12
- // COLLABORATIVELY, never solo. Until then, use the local stdio server (mcp/hermoso-mcp.mjs) + the CLI + skills.
12
+ // COLLABORATIVELY, never solo. Until then, use the local stdio server (mcp/heist-mcp.mjs) + the CLI + skills.
13
13
  //
14
14
  // When the cloud step happens, the remaining work is small and explicit (see ENABLE CHECKLIST at the bottom).
15
15
  // ───────────────────────────────────────────────────────────────────────────────────────────────────────
@@ -22,19 +22,19 @@ import { mcpCtx } from './client.mjs';
22
22
  // Mount the remote connector onto the Express app. No-op unless explicitly enabled + auth-backed.
23
23
  // `verifyBearer(token) -> {userId, accountId, email} | null` MUST be supplied by the caller (the real auth seam).
24
24
  export function mountRemoteMcp(app, { verifyBearer, publicBaseUrl } = {}) {
25
- if (process.env.HERMOSO_MCP_REMOTE !== '1') return false; // gate 1: off by default
25
+ if (process.env.HEIST_MCP_REMOTE !== '1') return false; // gate 1: off by default
26
26
  if (typeof verifyBearer !== 'function') { // gate 2: refuse without real auth
27
27
  console.error('[mcp-remote] REFUSING to mount: no token verifier wired. A remote, money-spending MCP must authenticate every caller (no-anon-spend). Wire Firebase Auth → verifyBearer first.');
28
28
  return false;
29
29
  }
30
- const BASE = (publicBaseUrl || process.env.HERMOSO_PUBLIC_URL || '').replace(/\/+$/, '');
30
+ const BASE = (publicBaseUrl || process.env.HEIST_PUBLIC_URL || '').replace(/\/+$/, '');
31
31
 
32
32
  // RFC 9728 protected-resource metadata — tells Claude.ai where to get a token. (Authorization-server metadata
33
33
  // is served by the auth provider itself, e.g. Firebase/your IdP.) Scopes match the AS metadata + minted token
34
34
  // (mcp/oauth.mjs): hermoso.research / hermoso.generate.
35
35
  app.get('/.well-known/oauth-protected-resource', (req, res) => res.json({
36
36
  resource: `${BASE}/mcp`,
37
- authorization_servers: [process.env.HERMOSO_OAUTH_ISSUER].filter(Boolean),
37
+ authorization_servers: [process.env.HEIST_OAUTH_ISSUER].filter(Boolean),
38
38
  scopes_supported: ['hermoso.research', 'hermoso.generate'],
39
39
  bearer_methods_supported: ['header'],
40
40
  }));
@@ -109,8 +109,13 @@ export function mountRemoteMcp(app, { verifyBearer, publicBaseUrl } = {}) {
109
109
  };
110
110
  // `?tools=research,create` narrows the roster this connection advertises (see registerTools). Read here rather
111
111
  // than inside registerTools so BOTH the anonymous discovery handshake and a real session honour the same query,
112
- // and so an unknown group is refused at the door with the valid list instead of silently serving all 301.
113
- // The scope is fixed at initialize and stored on the session: tools/list must not change under a live client.
112
+ // and so an unknown group is refused at the door with the valid list instead of silently serving every group.
113
+ // ABSENT, the DEFAULT is every group except `ads` (2026-08-16) 238 tools and ~66% of the schema weight, which
114
+ // takes an eagerly-loading client from ~235k tokens to ~83k. `?tools=all` restores the full roster.
115
+ // The scope fixed here is the STARTING roster, not a cage: `enable_tools` widens it mid-session and the SDK
116
+ // notifies the client. That is deliberate — the old comment's "tools/list must not change under a live client"
117
+ // was the right instinct for a scope the SERVER changes silently, and the wrong one for a change the CLIENT
118
+ // asked for and is told about.
114
119
  function scopeFor(req, res) {
115
120
  const { groups, error } = parseToolScope(req.query?.tools ?? req.headers['x-hermoso-tools']);
116
121
  if (error) { res.status(400).json({ jsonrpc: '2.0', error: { code: -32602, message: error }, id: null }); return false; }
@@ -179,7 +184,7 @@ export function mountRemoteMcp(app, { verifyBearer, publicBaseUrl } = {}) {
179
184
  }
180
185
  // The caller's bearer rides into every /api call the tools make — spend bills THEIR account. `remote: true`
181
186
  // says what this store IS: a per-request tenant scope on a shared, multi-tenant process. client.mjs treats the
182
- // presence of this store as the signal to STOP falling back to the process's own HERMOSO_PROFILE / HERMOSO_OWNER,
187
+ // presence of this store as the signal to STOP falling back to the process's own HEIST_PROFILE / HEIST_OWNER,
183
188
  // which belong to whoever runs the box, not to whoever is calling.
184
189
  //
185
190
  // NOTE WHAT IS DELIBERATELY *NOT* HERE: a profile or an owner read off the request. There is nowhere honest to
@@ -190,14 +195,14 @@ export function mountRemoteMcp(app, { verifyBearer, publicBaseUrl } = {}) {
190
195
  await mcpCtx.run({ token, remote: true }, () => entry.transport.handleRequest(req, res, req.body));
191
196
  });
192
197
 
193
- console.error(`[mcp-remote] mounted at ${BASE || '(set HERMOSO_PUBLIC_URL)'}/mcp`);
198
+ console.error(`[mcp-remote] mounted at ${BASE || '(set HEIST_PUBLIC_URL)'}/mcp`);
194
199
  return true;
195
200
  }
196
201
 
197
202
  // ── ENABLE CHECKLIST (cloud step, collaborative) ──────────────────────────────────────────────────────────
198
- // 1. Provision a hosted origin (Cloud Run) + Firebase Auth; set HERMOSO_PUBLIC_URL + HERMOSO_OAUTH_ISSUER.
203
+ // 1. Provision a hosted origin (Cloud Run) + Firebase Auth; set HEIST_PUBLIC_URL + HEIST_OAUTH_ISSUER.
199
204
  // 2. Implement verifyBearer(token) via the Firebase auth adapter (adapters/auth/firebase.js) and pass it here.
200
205
  // 3. Thread the authenticated user into mcp/client.mjs's outbound /api calls (AsyncLocalStorage) so reserve()/
201
206
  // gateSpend bill the right account — the server-side enforcement is already authoritative once req.user is real.
202
- // 4. Set HERMOSO_MCP_REMOTE=1. Then in server.js: `import { mountRemoteMcp } from './mcp/http.mjs'; mountRemoteMcp(app, { verifyBearer, publicBaseUrl })`.
203
- // 5. The published connector URL becomes `${HERMOSO_PUBLIC_URL}/mcp` — paste into Claude.ai → Settings → Connectors.
207
+ // 4. Set HEIST_MCP_REMOTE=1. Then in server.js: `import { mountRemoteMcp } from './mcp/http.mjs'; mountRemoteMcp(app, { verifyBearer, publicBaseUrl })`.
208
+ // 5. The published connector URL becomes `${HEIST_PUBLIC_URL}/mcp` — paste into Claude.ai → Settings → Connectors.