MCP server
Connect Claude, ChatGPT, Cursor, VS Code, Codex or any MCP client to your Fictura app — it reads your live paywalls, funnel, experiments, errors and docs, then writes integration code against your real setup instead of guessing.
An AI coding agent that has never seen your dashboard will invent a placement name, hardcode a price, and leave YOUR_API_KEY in the file. Connect it to this server and it reads the real thing first.
The server lives at https://mcp.fictura.co/mcp and speaks Streamable HTTP. https://api.fictura.co/mcp is the same server, so a config that already names it keeps working.
What an agent can change
It reads by default. An agent can see your revenue and your error stacks; it cannot make a paywall live, start an experiment, or change a setting. A handful of tools write, each one named here, each one narrow, and each one behind something you turn on:
- Drafts.
create_paywall_draft,save_paywall_doc,create_onboarding_draftandsave_onboarding_doccreate and edit draft paywalls and onboarding flows. A live one is refused. Every save is a new version, notedmcp: …in History, so you can roll it back. Going live stays a click in the dashboard. - The editor you have open.
editor_set_docputs a document on a Studio canvas afterpair_editorconnects to it with the 6-character code under Pair an agent. Nothing is saved: you see the change, can undo it, and save it yourself.unpair_editorends the pairing. - Error issues.
update_error_issueresolves, ignores, reopens, assigns, prioritises or adds a note to an issue — only when Issues settings → Let MCP keys change issues is on for the app. - AppsFlyer import.
start_appsflyer_importstarts the same import as the Start button under Setup → Import data, andconnect_appsflyerconnects AppsFlyer with its API token. Both need an MCP key minted with Can connect and import data.connect_appsflyeris never offered to a signed-in or keyless connection, because it takes a credential as an argument — connect AppsFlyer in the dashboard instead.
The draft and editor tools need a connection that can edit: an MCP key minted with Can edit drafts, or a sign-in where you chose read and edit drafts. A read-only connection still lists them, and calling one answers with how to get that access.
Two ways to authenticate
Data tools need one of these. The documentation tools need neither.
Sign in with Fictura
Add the URL with no headers. The first time the agent calls a data tool, your client shows a Sign in or Connect prompt. Log in to Fictura, pick one app, choose the access — read only, or read and edit drafts — and approve.
One sign-in connects one app. To switch apps, remove the connection, add it again, and pick the other app when you sign in. This is the way in for clients a person is sitting in front of: claude.ai, Claude Desktop, the Claude mobile apps, ChatGPT, Claude Code, Codex, Cursor and VS Code.
MCP key
Dashboard → Setup → Coding agents (MCP) → create a key. It is shown once, stored only as a hash, and revoking it takes effect on the next request. Send it either way:
Authorization: Bearer fk_mcp_YOUR_KEY
x-api-key: fk_mcp_YOUR_KEYThe key names the app, so there is no app id anywhere in the setup — and no way for one key to read another app's data. A key reads only, unless you tick Can edit drafts (it then starts fk_mcpw_) or Can connect and import data when you make it. Neither can be added to a key later.
Keys suit everything without a person to click Sign in: Cursor, VS Code, Windsurf, Codex and Claude Code configs you share or script, CI, and the OpenAI Responses API and Agents SDK.
Either way, the app needs an active Fictura subscription and the AI plan (or its own Anthropic key) before its data answers over MCP.
Connect
Both buttons add the URL only, so the editor asks you to sign in the first time the agent needs your data. To use a key in VS Code instead, install with a key: VS Code asks for it once and keeps it in its secret storage.
The dashboard's Coding agents (MCP) card has the same buttons, and an Add to Cursor with your new key already in it.
Claude (claude.ai, Desktop and mobile)
- Settings → Connectors → Add custom connector.
- Name it
Ficturaand pastehttps://mcp.fictura.co/mcpas the URL. Leave the advanced settings empty. - Press Connect when Claude asks, and sign in.
Claude Desktop can also take a key through the stdio bridge.
ChatGPT
- Open Plugins, press + and choose Add custom MCP server.
- Name it
Ficturaand enterhttps://mcp.fictura.co/mcpunder Connection. - Choose OAuth for authentication, accept the warning and create it. ChatGPT sends you to Fictura to sign in.
ChatGPT connects by signing in only; it does not send custom headers, so there is no key option here.
Claude Code
claude mcp add --transport http fictura https://mcp.fictura.co/mcpThen run /mcp inside Claude Code, pick fictura and sign in.
Add --scope user to have it in every project, or --scope project to share it through .mcp.json.
Codex
codex mcp add fictura --url https://mcp.fictura.co/mcpCodex sees that the server takes sign-in and opens the browser. If it does not, run codex mcp login fictura.
Cursor
~/.cursor/mcp.json:
{
"mcpServers": {
"fictura": {
"url": "https://mcp.fictura.co/mcp"
}
}
}Cursor asks you to sign in the first time the agent needs your data.
VS Code
.vscode/mcp.json:
{
"servers": {
"fictura": {
"type": "http",
"url": "https://mcp.fictura.co/mcp"
}
}
}Windsurf
Windsurf connects with a key:
{
"mcpServers": {
"fictura": {
"serverUrl": "https://mcp.fictura.co/mcp",
"headers": { "Authorization": "Bearer fk_mcp_YOUR_KEY" }
}
}
}Claude Desktop with a key
The custom connector above is the simplest route. To connect Claude Desktop with a key instead, use the @fictura/mcp stdio bridge. In claude_desktop_config.json:
{
"mcpServers": {
"fictura": {
"command": "npx",
"args": ["-y", "@fictura/mcp"],
"env": { "FICTURA_MCP_KEY": "fk_mcp_YOUR_KEY" }
}
}
}The bridge defines no tools of its own — it only translates stdio to HTTP, so it never falls behind the server. FICTURA_API_BASE points it somewhere other than production.
OpenAI Responses API and Agents SDK
Your code is the client here, so it sends a key. In the Responses API, Fictura is an mcp tool:
import os
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-6-astra",
tools=[
{
"type": "mcp",
"server_label": "fictura",
"server_url": "https://mcp.fictura.co/mcp",
"headers": {"Authorization": f"Bearer {os.environ['FICTURA_MCP_KEY']}"},
"require_approval": "never",
}
],
input="Which paywall is live at each placement, and how is trial-to-paid trending?",
)
print(response.output_text)require_approval: "never" suits a read-only key. With a key that can edit, leave the default ("always") or list only the read tools under {"never": {"tool_names": [...]}}.
In the Agents SDK, connect over Streamable HTTP:
import asyncio
import os
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp
async def main():
async with MCPServerStreamableHttp(
name="fictura",
params={
"url": "https://mcp.fictura.co/mcp",
"headers": {"Authorization": f"Bearer {os.environ['FICTURA_MCP_KEY']}"},
},
) as fictura:
agent = Agent(name="Growth analyst", mcp_servers=[fictura])
result = await Runner.run(agent, "Why did trial-to-paid drop this week?")
print(result.final_output)
asyncio.run(main())Documentation works without signing in
search_docs, read_doc and list_docs answer whether or not you have authenticated, so an agent can learn how Fictura works before anyone has signed up. Every other tool is listed too, so an agent can see what signing in would unlock; calling one without credentials is what makes your client ask you to sign in.
The docs an agent reads are the same pages you are reading, regenerated on every change to the product — so it is never working from a stale copy of the API.
Tools
Access says what a tool needs: none runs before sign-in, read works on any connection to the app, edit needs a connection that can edit drafts.
Documentation
| Tool | What it answers | Access |
|---|---|---|
search_docs | Full-text search across these docs, grouped by page. | none |
list_docs | Every page path by section, with a one-line description. | none |
read_doc | The full Markdown of one page; a wrong path returns the closest real ones. | none |
Setup and integration
| Tool | What it answers | Access |
|---|---|---|
get_app_setup | API base, publishable SDK key, RevenueCat webhook URL, bundle id. The one to call before generating any integration code. | read |
get_setup_status | Setup checklist, every feature toggle, connected integrations. | read |
get_link_infrastructure | Deep-link setup: Universal Links / App Links association, branded domains, web-to-app banner, ESP. | read |
get_push_config | Registered-device counts per cohort and whether OneSignal is configured. | read |
list_store_products | App Store Connect subscriptions, optionally with Apple's price ladder. | read |
Analytics and users
| Tool | What it answers | Access |
|---|---|---|
get_analytics | Overview, conversion funnel, cohort compare, or the split by store and country. | read |
get_tracking_plan | Every event the app sends, with volume, last-seen and kill-switch state. | read |
list_users · get_user | The user roster, and one user's stitched identity, events and churn state. | read |
check_entitlement | Whether a given user is subscribed, unified across Stripe, RevenueCat, App Store and Play. | read |
Paywalls, onboarding and experiments
| Tool | What it answers | Access |
|---|---|---|
list_surfaces | Paywalls, onboarding flows, what is live per placement, and the valid placement names. | read |
get_surface | One paywall or onboarding config, with version history. | read |
list_templates | The built-in paywall and onboarding starter templates. | read |
list_experiments · get_experiment | Experiments with full Bayesian results. | read |
Studio documents
| Tool | What it does | Access |
|---|---|---|
get_paywall_doc · get_onboarding_doc | The JSON document a Studio paywall or builder onboarding flow is made of, with its status and version. | read |
validate_paywall_doc · validate_onboarding_doc | Checks a document with the same parser a save uses. Changes nothing. | read |
create_paywall_draft | Writes. A new draft paywall from a Studio template or a document. Never made live. | edit |
save_paywall_doc | Writes. Replaces a draft paywall's document as a new version. Refused for a live paywall. | edit |
create_onboarding_draft | Writes. A new draft onboarding flow from a document. Never made live. | edit |
save_onboarding_doc | Writes. Replaces a draft flow's document as a new version. Refused for a live flow. | edit |
Studio editor
| Tool | What it does | Access |
|---|---|---|
pair_editor | Connects to a Studio editor someone has open, with the code under Pair an agent. A code works once, for 10 minutes; a pairing lasts 24 hours. | read |
editor_get_doc | The document exactly as it is on screen, unsaved changes included, with the selected screen and node. | read |
editor_set_doc | Writes to the screen, not the database. Puts a document on the canvas; the person saves it. | edit |
unpair_editor | Ends the pairing. | read |
Attribution and acquisition
| Tool | What it answers | Access |
|---|---|---|
get_attribution_report | Attribution summary, KPIs by source/campaign/link/day, or raw installs. | read |
get_acquisition_report | ROAS, raw ad spend, or tracking links. | read |
list_campaigns | Live campaigns from a connected ad platform. | read |
list_creators | Creator records and their 28-day funnel. | read |
get_store_analytics | App Store and Google Play numbers synced from Appfigures: downloads, store revenue, ratings, reviews, chart positions. | read |
get_appsflyer_import_status | Whether AppsFlyer is connected, the latest import run, and whether one ever finished. | read |
get_appsflyer_attribution_summary | What the AppsFlyer import brought in, by media source and campaign. | read |
start_appsflyer_import | Writes. Starts or resumes the AppsFlyer import in the background. | key with Can connect and import data |
connect_appsflyer | Writes. Connects AppsFlyer with its API token. Listed only to keys that can import. | key with Can connect and import data |
Errors and tracing
| Tool | What it answers | Access |
|---|---|---|
list_error_issues · search_error_issues | The error feed, or issues matching text, platform or release. | read |
get_error_issue | One issue with its recent occurrences, stack traces and daily counts. | read |
get_error_issue_brief | One issue condensed for fixing it, with a ready-to-use fix prompt. | read |
get_trace | Every span of one distributed trace, as a tree. | read |
update_error_issue | Writes. Resolve, ignore, reopen, assign, prioritise or add a note. | read, with Let MCP keys change issues on |
There is also an integrate_fictura_sdk prompt that walks an agent through a correct first integration.
What it deliberately cannot see
Push tokens and individual recipients (they identify people, and counts answer the same question), your OneSignal REST key, integration credentials such as the AppsFlyer token, and your Supabase token. App Builder projects are out of scope for now — that is Fictura's own agent, and pointing a second one at its working tree solves nothing.
Why this beats pasting docs into the chat
Three integration mistakes are near-universal, and all three are things only your account knows:
- Placement names. A
<GrowthPaywall placement="...">naming a placement that does not exist renders nothing and reports no error.list_surfacesreturns the names that are actually configured, alongside the ones your app has been observed sending — which is how you spot a typo you already shipped. - Deep links. Universal Links and App Links both fail silently: a malformed team id produces an association file that parses, associates nothing, and shows no error anywhere, so links keep opening the browser.
get_link_infrastructurereturnsios_readyandandroid_readyrather than making an agent guess. - Prices. Paywall configs never contain price strings — prices resolve at render from the store. An agent that has read
get_surfacesees that; one working from memory writes a literal and risks a rejected build.
Troubleshooting
401 Invalid or revoked Fictura credentials — something was sent and it does not work. A sign-in that expired or was revoked: sign in again (in Claude Code, /mcp → fictura). A key: it was revoked or pasted wrong, so mint a new one under Setup. With nothing sent at all, a data tool also answers 401 — that is the signal your client uses to show Sign in. A client that cannot sign in needs a key.
403 This Fictura connection is no longer linked to an app you can access — the sign-in still works but its app does not: it was archived, the grant was revoked, or you were removed from the app. Remove the connection, add it again, and pick an app when you sign in.
402 — the app is not allowed MCP access yet. It either needs an active Fictura subscription (dashboard → Billing), or it is on the standard plan and needs the AI plan or its own Anthropic key. If the message says the month's AI allowance is used up, adding your own Anthropic key keeps it going.
406 Not Acceptable — the client sent no Accept header, or only one of the two required values. Streamable HTTP needs Accept: application/json, text/event-stream. Every supported client does this for you; it usually shows up when testing with curl.
405 Method Not Allowed — the endpoint accepts POST only. There is no session to resume and no server-initiated stream, so GET and DELETE have nothing to do.
An MCP key rejected by the Links API — that is deliberate. fk_mcp_… keys authenticate /mcp and nothing else; the Links API takes fk_live_…. Neither surface accepts the other's key, so a key handed to an agent can only do what this page lists. See Authentication.