MCP

One URL and a key.

FormulaSignal runs a remote MCP server on the Streamable HTTP transport at https://formulasignal.com/mcp. There is nothing to install, no package to trust, and no code of ours running on your machine. 18 tools, the same 18 capabilities the REST API exposes, with the same key and the same limits.

Set it up in your client

Three clients are written out below because they take a remote MCP server and a custom header directly, which is what this endpoint needs. Each block is the shape that client’s own current documentation specifies. What was checked for each is stated with it, because “supported” is a word that costs somebody an afternoon when it turns out to mean “probably”.

Claude Code

One command, and it accepts the header directly.

CLAUDE CODE

claude mcp add --transport http formulasignal \
  https://formulasignal.com/mcp \
  --header "Authorization: Bearer $FORMULASIGNAL_API_KEY"

Then claude mcp list to confirm it connected, and ask it something: Resolve C4 Original and tell me its current caffeine amount with evidence. To commit the server to a repository instead, put it in .mcp.json. The type field is required whenever there is a url; without it the entry is read as a stdio server.

.mcp.json

{
  "mcpServers": {
    "formulasignal": {
      "type": "http",
      "url": "https://formulasignal.com/mcp",
      "headers": {
        "Authorization": "Bearer fsk_live_..."
      }
    }
  }
}

Verified end to end against the live endpoint: added, connected, tools listed, and a Record tool and a Signal tool both called.

Cursor

~/.cursor/mcp.json for every project, or .cursor/mcp.json in one repository. Cursor interpolates ${env:NAME}, so the key can stay in your environment rather than in the file.

~/.cursor/mcp.json

{
  "mcpServers": {
    "formulasignal": {
      "url": "https://formulasignal.com/mcp",
      "headers": {
        "Authorization": "Bearer ${env:FORMULASIGNAL_API_KEY}"
      }
    }
  }
}

VS Code

.vscode/mcp.json, and note the top-level key is servers rather than mcpServers. An input makes VS Code prompt for the key once and keep it out of the file entirely.

.vscode/mcp.json

{
  "inputs": [
    {
      "type": "promptString",
      "id": "formulasignal-key",
      "description": "FormulaSignal API key",
      "password": true
    }
  ],
  "servers": {
    "formulasignal": {
      "type": "http",
      "url": "https://formulasignal.com/mcp",
      "headers": {
        "Authorization": "Bearer ${input:formulasignal-key}"
      }
    }
  }
}

For Cursor and VS Code the configuration above is the shape each client’s current documentation specifies, and the exact request it produces was replayed against the live endpoint: handshake, tool list and tool call all answered. The editors themselves were not driven by hand, so if a version of either one changes its config format, that is the part to check first.

Claude Desktop, and why it is not in the list

A custom connector in Claude Desktop takes a URL and, under advanced settings, an OAuth client id and secret. There is no field for an arbitrary header, and FormulaSignal authenticates with a bearer key rather than running an OAuth server, so a custom connector cannot carry a FormulaSignal key today. Use the stdio bridge below, which does work, or one of the three clients above. This is written down rather than left out because discovering it yourself takes twenty minutes.

Any other client that speaks remote MCP

The three above are the ones that were checked. Any other client supporting the Streamable HTTP transport with custom headers takes the same two values, whatever it calls the fields.

TYPICAL SHAPE

{
  "mcpServers": {
    "formulasignal": {
      "type": "http",
      "url": "https://formulasignal.com/mcp",
      "headers": {
        "Authorization": "Bearer fsk_live_..."
      }
    }
  }
}

If your client rejects Authorization, send the key as X-FormulaSignal-Key with no Bearer prefix. Both headers are checked identically. That alternative exists because an MCP client config is a JSON file a person edits by hand, and “remember to write Bearer in front of it” is a support ticket waiting to happen.

Clients that only speak stdio

Most MCP clients are still stdio only. mcp-remote is the community bridge for exactly this and it is not ours: it is a published npm package that proxies a local stdio server to a remote HTTP one.

{
  "mcpServers": {
    "formulasignal": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://formulasignal.com/mcp",
        "--header",
        "Authorization:${FS_AUTH}"
      ],
      "env": {
        "FS_AUTH": "Bearer fsk_live_..."
      }
    }
  }
}

The colon has no space after it and the space lives in the environment variable. That is a real quirk of how arguments are split, not a typo. If you would rather keep the key out of the process list, where any other user on the machine can read it, mcp-remote also takes --header-file with one Name: value per line.

mcp-remote is a third-party package and not ours. It is named because it is the bridge the MCP ecosystem actually uses, and because the alternative is telling people with a stdio-only client that they cannot connect at all.

Verify it by hand

The endpoint is ordinary HTTP, so you never have to guess whether the problem is your client or us. This is the whole handshake.

LIST THE TOOLS

curl -s https://formulasignal.com/mcp \
  -H "Authorization: Bearer $FORMULASIGNAL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools[].name'

CALL ONE

curl -s https://formulasignal.com/mcp \
  -H "Authorization: Bearer $FORMULASIGNAL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"formulasignal_resolve_product",
                 "arguments":{"identifier":"C4 Original"}}}'

What the transport does and does not do

  • Protocol versions: 2025-06-18, 2025-03-26, 2024-11-05. An unsupported MCP-Protocol-Version header is a 400 with the supported list in the body.
  • Stateless. No Mcp-Session-Id is issued, so there is no session to lose and nothing to resume. Every request carries its own key.
  • JSON, not SSE. Every answer is a single bounded result with nothing to stream, so a POSTed request gets application/json back. A GET returns 405, which is how the specification says a server declines to open a server-initiated stream.
  • No resources and no prompts. Both list empty on purpose. Everything here is a live call against the Record, and a resource would be a copy of Record state sitting in your context with no request behind it and no date on it.
  • Server-side only. A request carrying a cross-origin Origin header is refused, which is the DNS-rebinding protection the specification requires. A browser page cannot call this endpoint, and it should not: the key would be in the page.

What the agent gets

Tool descriptions are generated from the same capability manifest the REST API and the OpenAPI document read, so a tool cannot mean one thing here and another there. Each carries what it is for, what it cannot provide, and its limits, because those are the three things an agent gets wrong without them.

ToolUse it for
formulasignal_search_recordAnswer one specific, bounded question from FormulaSignal's covered pre-workout Record.
formulasignal_resolve_productResolve a brand, product name, or alias to one canonical covered product.
formulasignal_get_product_recordReturn the controlled current Record for one covered product: identity, serving configuration, declared ingredients, captured price context, and freshness.
formulasignal_get_formula_historyReturn the controlled historical timeline for one covered product, with each state classified by what it can actually support.
formulasignal_explain_signalExplain one approved FormulaSignal Signal: a confirmed, reviewed change between two comparable product states.
formulasignal_list_signal_changesReturn approved FormulaSignal Signals in release order, newest first, for incremental polling.
formulasignal_compare_productsCompare two or three covered products on consistent FormulaSignal criteria.
formulasignal_get_serving_economicsReturn the captured commercial facts and the deterministic price arithmetic for one covered product.
formulasignal_get_research_contextReturn the dose range used in the selected evidence set for named ingredients, with the citation and its limitations.
formulasignal_get_regulatory_contextReturn regulatory records and documented cautions for named ingredients, each carrying the class of record it actually is.
formulasignal_get_category_snapshotReturn a bounded summary of approved Signals and coverage state across the covered pre-workout set for a date window.
formulasignal_list_ledger_editionsList the published Category Ledger editions: period, status, data-as-of date and the count released in each.
formulasignal_get_ledger_editionReturn one Category Ledger edition: the executive summary, confirmed changes released in the period, category benchmarks with their cohorts, serving economics, the product comparison, and the limitations.
formulasignal_list_watched_productsReturn the watchlist of the one Founding Pro account this key is bound to, with each product's monitoring state.
formulasignal_watch_productAdd one covered product to the bound account's watchlist.
formulasignal_unwatch_productRemove one product from the bound account's watchlist.
formulasignal_get_watch_receiptReturn the monitoring receipt for one period: valid checks, attempts that returned nothing, recoveries, and confirmed Signals.
formulasignal_get_record_versionReturn the public Record version, the methodology version, the supported category, coverage counts, and source freshness.

Four of these need a scope this page’s free key does not carry. list_ledger_editions and get_ledger_edition need ledger:read, and the three watch tools need watch:manage. They are listed because an agent should know they exist and refuse cleanly, rather than discover them by failing.

Instructing your agent

The tool descriptions already carry the semantics, so this is reinforcement rather than the load-bearing part. Never make correct use depend on a system prompt somebody can edit out.

SYSTEM PROMPT FRAGMENT

When answering about a supplement product:
- Resolve the name with formulasignal_resolve_product first. If it
  returns candidates, ask which one. Do not pick.
- Quote the observation date with any value you state.
- An observation window is not a change date. If asked exactly when a
  product changed, give the window and say the exact date is not
  established.
- An ingredient absent from a declared list is undisclosed, not absent.
  A null amount is not zero.
- Price per serving is a FormulaSignal calculation, not a manufacturer
  figure. Say so.
- Report a refusal as a fact about the Record, not about the product.
  "FormulaSignal has no confirmed change on file" is not "the product
  never changed".

The evidence model explains each of those rules and why the Record draws the line where it does.