FromlyDocumentation
Get started

MCP

Connect an assistant to a Fromly workspace over the Model Context Protocol. It can then read your contacts, emails, campaigns and flows — and, if you let it, draft new ones.

Claude Code

  1. 1

    Add the server

    One command, from any directory. Local is you alone in the current project; Project writes a .mcp.json your whole team shares through git; User follows you to every project.

    claude mcp add --transport http fromly https://api.fromly.io/v1/mcp

    --transport http matters: without it the command assumes a local process rather than a remote server.

  2. 2

    Sign in when asked

    The first time the assistant uses the server it opens your browser, you sign in, and you choose what it may do. Run /mcp inside Claude Code to start that at any point.

Check it worked

claude mcp list

Or ask the assistant something only this workspace can answer: “list my automations”. If it cannot connect, claude mcp get fromly reports why, and claude mcp remove fromly undoes the setup.

Claude Desktop

  1. 1

    Add a custom connector

    In Claude (desktop or claude.ai), open Settings → Connectors, then “Add custom connector”. Custom connectors are available on every plan, the free one included.

  2. 2

    Name it and paste the server URL

    Name: Fromly. URL:

    https://api.fromly.io/v1/mcp

    Leave “Use your own OAuth client” alone: Claude registers itself with the server. The default identity is the right one.

  3. 3

    Sign in

    Choose “Sign in now”: your browser opens, you sign in and pick what the assistant may do. On Team and Enterprise plans, an owner adds the connector first and members then connect to it themselves.

Check it worked

Start a conversation and ask something only this workspace can answer: “list my automations”. The connector also appears with its status under Settings → Connectors.

Cursor

  1. 1

    Create the config file

    ~/.cursor/mcp.json makes the server available everywhere; .cursor/mcp.json inside a project keeps it to that project. Either way:

    {
      "mcpServers": {
        "fromly": {
          "url": "https://api.fromly.io/v1/mcp"
        }
      }
    }
  2. 2

    Sign in when asked

    Cursor supports OAuth for servers that require it: the first use opens your browser, you sign in and choose what the assistant may do. For an unattended setup, pass an API key as a header instead:

    {
      "mcpServers": {
        "fromly": {
          "url": "https://api.fromly.io/v1/mcp",
          "headers": {
            "Authorization": "Bearer fk_live_…"
          }
        }
      }
    }

Check it worked

The server and its tools appear under Cursor Settings → MCP. Then ask the agent something only this workspace can answer: “list my automations”.

ChatGPT

  1. 1

    Turn on developer mode

    In ChatGPT settings, under Apps & Connectors, enable Developer mode. It needs a paid plan, and on Business and Enterprise a workspace admin has to allow it first.

  2. 2

    Add the connector

    Create a new connector: name it Fromly, paste the server URL below, keep OAuth as the authentication, acknowledge the prompt and create it.

    https://api.fromly.io/v1/mcp
  3. 3

    Sign in

    ChatGPT opens the sign-in flow; you choose what the assistant may do.

    What the assistant can use depends on your ChatGPT plan: reading tools work broadly, while drafting tools are limited to the plans OpenAI allows write actions on. The article in the reference link is the source of truth.

Check it worked

In a conversation with developer mode on, pick the connector and ask something only this workspace can answer: “list my automations”.

Codex

  1. 1

    Add the server

    From any directory:

    codex mcp add fromly --url https://api.fromly.io/v1/mcp
  2. 2

    Sign in

    OAuth is an explicit step in Codex:

    codex mcp login fromly

    Your browser opens, you sign in and choose what the assistant may do.

  3. 3

    Or use an API key

    For an unattended setup, point the server at an environment variable holding the key, in ~/.codex/config.toml:

    [mcp_servers.fromly]
    url = "https://api.fromly.io/v1/mcp"
    bearer_token_env_var = "FROMLY_API_KEY"

Check it worked

codex mcp list

Then ask the assistant something only this workspace can answer: “list my automations”.

Any MCP client

  1. 1

    Point your client at the server

    Clients configured by file take this shape. Where the file lives differs between them; check your client’s own documentation for its path.

    {
      "mcpServers": {
        "fromly": {
          "type": "http",
          "url": "https://api.fromly.io/v1/mcp"
        }
      }
    }

    Most desktop clients need a full restart before they pick up a change.

  2. 2

    Clients without OAuth

    A client that supports OAuth finds everything it needs from the endpoint itself. One that does not, or anything running unattended with no person to consent, passes an API key as a header instead:

    {
      "mcpServers": {
        "fromly": {
          "type": "http",
          "url": "https://api.fromly.io/v1/mcp",
          "headers": {
            "Authorization": "Bearer fk_live_…"
          }
        }
      }
    }

    The header field’s exact name varies by client. The level chosen when the key is created is what the assistant gets.

Check it worked

Ask the assistant something only this workspace can answer: “list my automations”. Read-only access is enough, and a real answer proves the connection, the endpoint and the permissions all line up.

Choosing what it can do

The assistant asks for a set of permissions; the consent screen offers the levels that fit inside what it asked for, and grants only the one you pick. The narrowest is selected for you.

LevelWhat the assistant can do
Read onlyAnswer questions about anything in the workspace. Change nothing.
Read and draftAlso compose emails, assemble campaigns, lay out flows and manage tags, for you to review and send.
Full accessAlso add or remove a tag on a contact, or put it on or take it off a list, which can start a live flow. See below.

Access is granted to the workspace you are currently in, and to that one only. Switching workspace and connecting again asks again.

What the assistant can do

An assistant is offered only the tools its access allows. Read-only access is not shown the ones that write — they are absent rather than refused, because a tool an assistant can see is a tool it will try, and listing what it cannot do invites a failure on every plan it makes.

Reading — any level

ToolWhat it does
list_workspacesThe workspaces this connection can reach. With several, every other tool takes a `workspace` argument.
list_contactsSearches contacts: by a fragment of name or address, exact emails, status, source, tags, dates, or the app’s filter syntax.
get_contactOne contact with its tags, billing facts and consent timestamps.
list_tagsEvery tag with its contact count. Stripe-derived tags are marked reserved.
get_tagOne tag, with the flows that name it.
list_listsEvery mailing list, with how many people can be emailed on each.
get_contact_listsThe lists one contact is on, and the ones they left.
list_emailsThe email library, most recently edited first.
get_emailOne email with its full content, ready to be revised.
email_block_typesEvery element an email may contain, with an example. Not needed when writing Markdown.
list_email_themesThe workspace’s themes, Base first, with their styles and which one is the default.
get_email_themeOne theme with its styles, ready to be revised.
email_theme_formatWhat a theme may hold: the parts of an email and the values each property accepts.
preview_email_themeThe HTML of a sample email in a theme, saved or not yet. Writes nothing.
list_email_componentsThe workspace’s headers and footers, with their blocks.
list_broadcastsCampaigns and their results, newest first.
get_broadcastOne campaign, with the email it would send.
list_automationsEvery flow in the workspace, with its status and how many people are in it.
flow_formatHow a flow graph is written: trigger and step types, wait limits, filter syntax. Read this before laying out a flow.
list_event_typesThe built-in events and this workspace’s own, with counts. A trigger compares names exactly.
automation_statsRun totals and per-step figures for one flow.
step_cohortThe people who entered one step, paged by an opaque cursor.
where_people_areHow many contacts are parked on each step, and how many are due to move.
why_runs_stoppedThe reasons runs of a flow ended early, grouped and counted.

Drafting — “Read and draft” or above

ToolWhat it does
upsert_contactCreates a contact, or updates the one already holding that address.
create_tagAdds a tag to the vocabulary without putting it on anyone. Nothing is sent.
rename_tagRenames a tag on every contact. Refused while a flow names it.
delete_tagDeletes a tag from every contact. Refused while a flow names it.
create_listCreates a mailing list nobody is on yet. Nothing is sent.
block_contactBlocks one contact: nothing reaches them, transactional emails included, until restored.
create_emailWrites a new email into the library. Nothing is sent.
update_emailRevises an email. Content is replaced whole.
create_broadcastDrafts a campaign and the email it carries. Always a draft.
update_broadcastRevises a campaign draft. Refused once it has been sent.
create_email_themeCreates a theme, ideally from the design tokens of the person’s code. Nothing is sent.
update_email_themeRevises a theme. Every email wearing it changes with it.
set_default_email_themeMakes a theme the default. Emails without a theme of their own change look.
create_email_componentCreates a header or footer from email blocks.
update_email_componentRevises a header or footer. Every email showing it changes.
create_automationLays out a flow. Always a draft — turning it on is a separate permission.
update_automationRevises a flow’s name or graph. The status cannot be changed.
record_eventRecords one event against a contact, creating the contact if it is unknown.

Tagging a contact or changing its lists: “Full access” only

ToolWhat it does
add_contact_tagTags one contact. Can send: a live flow triggered by the tag enrols them.
remove_contact_tagRemoves a tag from one contact. Nothing is sent.
add_contact_to_listPuts one contact on a list. Can send: a live flow started by the list enrols them.
remove_contact_from_listTakes one contact off a list. Nothing is sent.
restore_contactLifts a block or a bounce, so the contact can be emailed again. Never an unsubscribe or a spam complaint.

An agent writes an email as markdown, the simplest, or as content after reading email_block_types. Elements an email cannot contain are discarded on save, and the reply says so only in warnings.

An agent working in your code can give your emails its look: it reads your design tokens, creates a theme with create_email_theme, checks it with preview_email_theme, then applies it to an email with themeId or makes it the default. Rewriting an email keeps its theme, header and footer.

What it cannot do

No tool sends a campaign or turns on a flow. Both are absent from this surface entirely: not withheld from the lower levels, but missing even at full access.

They stay on the HTTP API, where they are an explicit call someone wrote on purpose. An assistant deciding on its own that now is the moment to email thousands of people is not a failure worth leaving open to save one request.

Tagging a contact or putting it on a list is the exception, and only at full access. Either can enrol that one contact in a live flow triggered by the tag or the list, so it can send to them. Its reach is one person, and the tool tells the assistant so before it acts. Someone who left a list from the unsubscribe page is never put back on it.

The one thing to weigh before granting the draft level is record_event. An event can trigger a flow that is already live, so recording one is not a read wearing different clothes.

Revoking access

claude mcp remove fromly, or the equivalent in your client, ends the connection from your side.

If you connected with an API key, revoke it in Settings → API. The assistant loses access on its next call — there is no session to expire and nothing cached.