MCP server
The MCP server is the programmatic interface to Hermoso. Two transports expose the identical toolset from the identical source file, so nothing is available on one and missing from the other.
In a coding agent you do not need it. Claude Code, Codex, Gemini CLI, Cursor and most other coding agents install Hermoso with one command, as five skills that drive the CLI and load no tool list into your sessions. In Claude Code: claude plugin marketplace add hermoso-ai/hermoso && claude plugin install hermoso@hermoso. Every agent’s command is in the Quickstart. Add the MCP server as well only if you want the tools in the agent’s own list.
Hosted (Streamable HTTP)
https://app.hermoso.ai/mcp is a remote MCP server with OAuth 2.0. The client identifies itself with a client ID metadata document (ChatGPT, Claude, VS Code) or registers dynamically, the user approves on a Hermoso consent page, and no key is ever created or pasted. No client ID or client secret is needed: if your client offers OAuth client fields, leave them empty.
In Claude on the web or desktop: Customize → Connectors → Add → Add custom connector, paste the URL and press Continue. Under Authentication choose Sign in now (Claude may pre-select “No sign-in”, which leaves your first request unsigned), leave the OAuth client on Claude’s published identity, press Add, then Connect and approve.
In ChatGPT: Plugins → + → Create app → Create MCP App. Name it Hermoso, keep Server URL and paste the URL, set Authentication to OAuth, leave Advanced OAuth settings as they are, tick I understand and want to continue and press Create, then approve on the Hermoso page that opens. Creating an app needs Developer mode switched on once, under Settings → Security and login.
In Meta Muse there is no connector settings screen: the chat is the setup. Send Muse one message: Add my Hermoso connector: https://app.hermoso.ai/mcp, followed by Setup: standard MCP over HTTP with OAuth, send me the sign-in link and I will approve it, Skill: save one so every question about marketing, ads, ad creative, competitor research and social media posting goes through Hermoso tools first, and Once you are in, list my connected channels and my credit balance. Muse registers itself, replies with a Hermoso connector card, and after you tap Connect and approve on the Hermoso page it saves the connector and the skill. Works from the Muse app, muse.ai and WhatsApp. Run end to end on 2026-09-28; the full walkthrough is at hermoso.ai/muse.
In Perplexity (Pro or Enterprise): in the left sidebar open Customize, then the Connectors tab → Custom connector (top right) → Add MCP connector. Name it Hermoso and paste the URL as the MCP server URL. Expand Advanced: Authentication OAuth (leave Client ID and Client Secret empty; Perplexity identifies itself with its own client metadata document), Transport Streamable HTTP, Network access Public. Tick I understand custom connectors can introduce risks and press Add. Then on the Hermoso card click + (Add connector) and approve on the Hermoso page. To use it in a chat: + (Add files or tools) → Connectors → tick Hermoso. Run end to end on 2026-09-28.
Claude Code accepts claude mcp add --transport http hermoso https://app.hermoso.ai/mcp. Until you sign in, claude mcp list reports ! Needs authentication; run claude mcp login hermoso once, approve on the Hermoso page it opens in your browser, and the server reports ✔ Connected. On a machine with no browser, claude mcp login hermoso --no-browser prints the link instead. In Claude Code the one-command install above is still the lighter path, since it loads no tool list into your sessions. Measured against Claude Code 2.1.282 on 2026-09-25.
Codex CLI takes the hosted URL and runs the OAuth flow by itself: codex mcp add hermoso --url https://app.hermoso.ai/mcp detects the sign-in, opens the Hermoso consent page and stores the token, and codex mcp login hermoso repeats it later. Measured against codex-cli 0.157.0 on 2026-09-25. The plugin install in the Quickstart is still the lighter option, because it loads no tool list.
Gemini CLI takes the hosted URL with gemini mcp add -t http hermoso https://app.hermoso.ai/mcp. Then type /mcp auth hermoso inside Gemini CLI, approve on the Hermoso page it opens, and the server lists its tools. Measured against Gemini CLI 0.61 on 2026-09-25. The extension install in the Quickstart is still the lighter option, because it loads no tool list.
For client developers. The OAuth server follows the MCP authorization spec: protected-resource metadata (RFC 9728) at both /.well-known/oauth-protected-resource and /.well-known/oauth-protected-resource/mcp, authorization-server metadata (RFC 8414) at /.well-known/oauth-authorization-server (there is no OpenID Connect document, because no ID token is issued), client ID metadata documents, and dynamic registration (RFC 7591). A registration that asks for none is a public client. One that asks for “client_secret_post” or “client_secret_basic”, or names no method, is issued a secret. PKCE with S256 is required. A resource parameter (RFC 8707) must name this server, or the answer is “invalid_target”. Every authorization response, errors included, carries iss equal to the metadata issuer (RFC 9207). Refresh tokens rotate on every use. The discovery, registration, token and /mcp endpoints answer CORS preflights, so a browser-based client can connect.
Sessions
The transport is session-based; the server returns an mcp-session-id on initialize and expects it on subsequent requests. Two behaviours are worth knowing because they affect how you write a long-lived client:
- A session the server no longer holds answers
404. Per the MCP spec that obliges your client to re-initialise, and ours does exactly that rather than returning a status a client cannot recover from. Handle 404 by starting a new session and carrying on. - An
initializecarrying a stale session id is honoured, not refused. Sessions are also evicted on idle. Neither case should ever brick a connection.
stdio
For any client that launches a subprocess and wants the tools in its own list. (A coding agent that only needs Hermoso to work is better served by the one-command install above: it costs no context until a command runs.) The published hermoso npm package means there is nothing to clone.
Sign in once with the CLI and the stdio server needs no key in any client config, because hermoso mcp reads the bearer stored by hermoso auth login before it hands stdout to the JSON-RPC channel. In Claude Code that is one line, and it reports Connected on the spot:
npm install -g hermoso && hermoso auth login && claude mcp add hermoso -- npx -y hermoso mcp
{ "mcpServers": { "hermoso": { "command": "npx", "args": ["-y", "hermoso", "mcp"] } } }
Pass the key explicitly instead when the process cannot read your home directory, for example in CI:
claude mcp add hermoso -e HERMOSO_TOKEN=hmk_… -- npx -y hermoso mcp
The mcp subcommand is required. Running npx -y hermoso with no subcommand prints the CLI help and exits, so a client configured without it spawns a process that writes usage text to the JSON-RPC channel and dies.
Environment
| Variable | Meaning |
|---|---|
HERMOSO_TOKEN | Your agent key (hmk_…). Required against the hosted app. |
HERMOSO_API_BASE | API origin. Defaults to https://app.hermoso.ai; set http://localhost:3000 to run against your own server. |
HERMOSO_PROFILE | Profile to act in. stdio only; see below. |
HERMOSO_OWNER | Owning account, for a brand another account shared with you. stdio only. |
Authentication
A Hermoso agent key, sent as a bearer token:
Authorization: Bearer hmk_…
There is no x-api-key header. If you have seen one referenced, it is the header we send to a third-party data provider, not one we accept.
Key properties, all deliberate:
- Stored as a SHA-256 hash; the plaintext is never persisted and is returned exactly once, at creation.
- No expiry. A key lives until it is revoked in the app. Rotate by creating a new one and revoking the old.
- A malformed or revoked
hmk_token is a hard401, never a silent downgrade to anonymous. - A key inherits the account's server-resolved plan and billing. Plan is never read from a request header.
- Up to 50 live keys per account. Every connection made through the hosted connector (Claude, ChatGPT, Codex, VS Code and the rest) mints one, named after the client, so revoke the ones you no longer use.
Profile scoping
One account holds many profiles. Which one a call acts in is resolved on the server, every request, from three inputs in order:
- The profile pinned to the key by
use_brand. - On stdio only,
HERMOSO_PROFILE/HERMOSO_OWNER. - Otherwise the account's default profile.
A pin is not a grant. Membership is re-checked on every single request, and it fails closed: an unreadable membership record denies rather than falling back to your own account. Access revoked after a pin was written stops working on the next call, not at the next login.
On the hosted transport the environment variables are not consulted at all: a hosted connector has no environment of yours, and honouring the server's would scope your call to whatever profile our host happens to name.
Discovery: never hard-code the roster
tools/list is always authoritative. Beyond that, two free calls tell an agent what is true right now:
hermoso_capabilities: the live model catalog with exact per-render credit costs, the recipe ids, and which channels this workspace has connected. Call it first.list_connectors: what is linked, what is offered but unlinked, and what needs reconnecting.
Both exist because a roster written into a prompt goes stale. Our own marketing pages have done exactly that, which is why the tool reference on this site is generated rather than typed.
Registry
| Where | Identifier |
|---|---|
| Official MCP registry | io.github.hermoso-ai/hermoso |
| npm | hermoso |
| Server card | /.well-known/mcp.json |
Spend safety
Worth knowing before you let an agent loose on an ad account, because it changes how much supervision the loop needs:
- Every campaign, ad group, ad and creative is created paused. There is no caller override on the Meta side.
- Turning something live is confirm-gated. So is creating an enabled child under an already-enabled parent, because that serves on the next auction.
- Build results are reported from a read-back of what the platform stored, never from what we sent.
- Destructive calls scale their friction with the blast radius: an empty workspace deletes in one call, one holding live connectors and a library requires you to echo back facts about the actual target.