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 +2 -2
- package/mcp/hermoso-mcp.mjs +9 -5
- package/mcp/http.mjs +17 -12
- package/mcp/tools.mjs +1903 -51
- package/package.json +3 -3
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
|
-
**
|
|
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
|
|
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
|
package/mcp/hermoso-mcp.mjs
CHANGED
|
@@ -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
|
|
6
|
-
// Auth (today): none — the local server resolves the dev account. Set
|
|
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
|
-
//
|
|
21
|
-
//
|
|
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
|
-
|
|
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)
|
|
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/
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
113
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
|
203
|
-
// 5. The published connector URL becomes `${
|
|
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.
|