> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stefanbrain.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Follow the content contract in AGENTS.md.
> Treat text as the canonical explanation; video and screenshots enhance it.
> Do not publish unverified product behavior or duplicate an existing canonical article.

# StefanBrain MCP

> Connect the StefanBrain product MCP to Codex, Claude, ChatGPT, Cursor, Windsurf, or another MCP client.

StefanBrain exposes a remote MCP server over streamable HTTP:

```text theme={"system"}
https://stefanbrain.com/api/developers/v1/mcp
```

<Note>
  This page documents the **StefanBrain product MCP**, which can run StefanBrain tools and complete work. It is separate from the read-only documentation MCP at `https://docs.stefanbrain.com/mcp`.
</Note>

Use OAuth for connector interfaces (claude.ai, Claude Desktop, ChatGPT) and for coding assistants that support MCP OAuth, such as Claude Code. Use an API key for clients that send a fixed header (Cursor, Windsurf, Codex) and for SDKs and scripts. Both bill the plan pool on MCP.

## Use `ask_stefanbrain`

`ask_stefanbrain` is the main MCP tool. In your assistant, say **use StefanBrain** or **use SB**, then describe the work. Your assistant calls the tool; StefanBrain completes the turn on its servers and returns the response.

You can use it to:

* Request ad angles, copy, reviews, strategy, research, or another complete work product.
* Send up to 10 image URLs through `image_urls` for JPEG, PNG, WebP, or GIF inputs.
* Upload local images, PDFs, documents, and spreadsheets through `POST /api/developers/v1/files`, then pass the returned `upload_...` identifiers in `file_ids`. The upload needs an unscoped key or a key with the `runs` scope. A key scoped only to `mcp` gets `403 api_key_scope_forbidden`; call `stage_file` instead.
* Call `stage_file` when the MCP client cannot send raw HTTP. Stage a public URL or base64-encoded bytes, then pass its `upload_id` in `file_ids`.
* Include a video or web-page URL in `message`; StefanBrain detects it without a separate URL field.

Follow-up asks continue the same StefanBrain chat. To start a fresh one, the assistant passes `chat: "new"`.

Uploads for `file_ids` expire after 24 hours. One ask accepts at most 10 attachments across `image_urls` and `file_ids`, and its `message` can be up to 32,000 characters. Each image URL must be a public JPEG, PNG, WebP, or GIF of 15 MB or less.

Inside an ask or an Agent Run, StefanBrain never sends email or changes schedules, whichever lane pays for the turn. `prepare_launch` only stages a draft. Open the reply's `app_url` to finish those in the app. Calling `send_email` directly sends immediately.

Leave `ask_stefanbrain.brain` unset. It is for StefanBrain admins. For any other account, the call fails with an error that tells you to omit it.

Use a direct tool when you need its raw result. Use `ask_stefanbrain` when StefanBrain should plan and complete the work. `ask_stefanbrain` has no JSON-schema option; it returns free text in `answer`. For schema-constrained JSON, start an [Agent Run](/developers/agent-runs) with `output_config.format`.

### Read the answer

`ask_stefanbrain` waits up to about 55 seconds for the turn. A turn that finishes in that window returns its answer inline. Otherwise the call returns a parked result with `family: "mcp_ask"`, a `run_…` `run_id`, and `status` `queued` or `running`. Poll `get_job_status` with that `family` and `run_id` about every 10 seconds, then call `get_job_result` when `status` is `succeeded`, `failed`, or `cancelled`.

Set the client's tool timeout above 60 seconds. Clients that send an MCP `progressToken` get progress notifications while the ask waits. Use `cancel_job` to stop a queued or running `mcp_ask` job.

An ask returns `answer`, `chat`, and `app_url`, plus `steps` and `produced_documents` when the turn has them. A parked ask's `get_job_result` returns the same fields, with `status` and `last_error`. `produced_documents` holds the text files the turn wrote, up to 16,000 characters each and 48,000 in total. `answer` repeats the steps and files for clients that read only text.

### Handle a busy chat

An ask uses `on_busy: "reject"` by default. If the target chat has an active turn, the result is a `chat_busy` receipt with the active run details.

Only `on_busy: "reject"` is supported. Poll the blocking run and retry after it finishes, or use a fresh chat.

If StefanBrain is briefly at capacity when an ask starts, the call returns a retryable error (`upstream_unavailable` in the tool result on MCP, `429 runtime_capacity_busy` with `Retry-After` on REST). Retry after a few seconds.

Use a different `session` label for independent parallel work. Queue only when the ask needs the existing chat state.

### Retry an ask safely

Pass `idempotency_key` before retrying an ask whose response may have been lost. A repeated key returns the original turn and `run_id` without another charge.

Ask deduplication holds for the rest of the same UTC day. Keep the same `project`, `session`, or explicit `chat` on every retry.

## Billing and transport behavior

For standard member accounts, MCP calls use the plan's monthly pool instead of the API wallet. This applies to OAuth tokens and API keys used on MCP.

Contracted partner accounts can have plan-pool traffic billed to the API wallet under their agreement. `account_status` does not show which lane applies; confirm it with your StefanBrain contact.

* Use MCP for interactive work inside an assistant.
* Use the REST API for integrations, pipelines, and automated or machine-scale traffic.

The MCP server is stateless and does not issue an `Mcp-Session-Id`. Each client message is one authenticated `POST`, so reconnecting is safe. A standalone `GET` request returns `405` by design; the server does not send server-initiated messages.

## Connect Codex

Export the API key in your environment:

```bash theme={"system"}
export STEFANBRAIN_API_KEY="stefan_sk_your_key_here"
```

Add the server to `~/.codex/config.toml`:

```toml theme={"system"}
[mcp_servers.stefanbrain]
url = "https://stefanbrain.com/api/developers/v1/mcp"
bearer_token_env_var = "STEFANBRAIN_API_KEY"
```

<Warning>
  Keep the key out of committed configuration. The configuration above reads it from `STEFANBRAIN_API_KEY` instead of storing the secret in the file.
</Warning>

## Connect Claude Code

```bash theme={"system"}
claude mcp add --transport http --scope user stefanbrain \
  https://stefanbrain.com/api/developers/v1/mcp
claude mcp login stefanbrain
```

Claude Code opens the StefanBrain OAuth flow. Remove `--scope user` when the server should apply only to the current project.

To use an API key instead, send it as a header:

```bash theme={"system"}
claude mcp add --transport http --scope user stefanbrain \
  https://stefanbrain.com/api/developers/v1/mcp \
  --header "Authorization: Bearer $STEFANBRAIN_API_KEY"
```

## Connect Cursor

Add this to `.cursor/mcp.json` for one project or `~/.cursor/mcp.json` globally:

```json theme={"system"}
{
  "mcpServers": {
    "stefanbrain": {
      "url": "https://stefanbrain.com/api/developers/v1/mcp",
      "headers": {
        "Authorization": "Bearer ${env:STEFANBRAIN_API_KEY}"
      }
    }
  }
}
```

## Connect Windsurf

Add the server to `~/.codeium/windsurf/mcp_config.json`. Windsurf uses `serverUrl`, not `url`.

```json theme={"system"}
{
  "mcpServers": {
    "stefanbrain": {
      "serverUrl": "https://stefanbrain.com/api/developers/v1/mcp",
      "headers": {
        "Authorization": "Bearer ${env:STEFANBRAIN_API_KEY}"
      }
    }
  }
}
```

## Connect an OAuth client

StefanBrain operates an OAuth 2.1 authorization server for the MCP endpoint. In claude.ai, Claude Desktop, ChatGPT, or another custom-connector interface:

1. Add a custom connector.
2. Paste `https://stefanbrain.com/api/developers/v1/mcp` as the connector URL.
3. Sign in to StefanBrain if requested.
4. Select **Allow access**.

Connecting requires an active StefanBrain plan or trial. Without one, the approval page shows **Developer access required**, and API-key calls return `403 developer_access_forbidden`.

You do not need an API key, Client ID, or advanced settings for this flow. Disconnect the connector in the client to revoke its access. Access tokens last one hour and refresh automatically. Refresh tokens rotate on use and expire 60 days after issue. StefanBrain has no in-app list of connected OAuth clients.

If a previously configured connector reports that it cannot register with StefanBrain's sign-in service, remove it and add it again.

## Connect another streamable-HTTP client

Most remote MCP clients accept this shape:

```json theme={"system"}
{
  "type": "http",
  "url": "https://stefanbrain.com/api/developers/v1/mcp",
  "headers": {
    "Authorization": "Bearer stefan_sk_your_key_here"
  }
}
```

Header clients can send `x-api-key: stefan_sk_...` instead of `Authorization: Bearer`.

Clients that only support stdio can bridge with `mcp-remote`:

```bash theme={"system"}
npx -y mcp-remote https://stefanbrain.com/api/developers/v1/mcp --header "Authorization: Bearer ${STEFANBRAIN_API_KEY}"
```

## Available tool areas

The MCP surface serves the supported developer-tool catalog. `tools/list` is authoritative for the tools available to the authenticated user in the current deployment.

Tool availability can depend on account permissions, connector access, and deployment configuration. Do not treat this summary as an exhaustive static list.

* **Ask StefanBrain:** `ask_stefanbrain`
* **Chats, projects, products, and brands:** `search_chats`, `read_chat`, `list_chats`, `list_projects`, `get_project_context`, `manage_projects`, `manage_products`, and `manage_brands`
* **Research:** `web_search`, `web_extract`, `agentcore_browser`, `website_traffic`, `amazon_search`, `amazon_product`, `google_trends`, `reddit_search`, `reddit_thread`, and `scrape_social_comments`
* **Meta:** `get_meta_ad` reads one public Ad Library ad as an asynchronous job. `meta_ad_accounts_search` and `meta_ads_insights` are served directly and need a connected Meta account.
* **Connected accounts:** Connector tools are not served directly over MCP. Ask for connector work with `ask_stefanbrain`, which runs a full StefanBrain turn with your connected accounts.
* **Copy, creative, and research jobs (asynchronous):** `review_copy`, `find_angles`, `create_images`, `edit_image`, `review_funnel`, and `research_shortform`
* **Jobs:** `get_job_status`, `get_job_result`, and `cancel_job`
* **Files:** `stage_file` for a public URL or base64 payload that should become an `upload_...` id
* **Launch:** `upload_creative` adds one image (a public URL, a staged `upload_...` id, or a file path in the chat) to your Launch library and returns its `staticCreativeId`; `prepare_launch` stages a Meta launch draft that you review and launch in the app
* **Email:** `send_email` sends real email immediately from a fixed StefanBrain sender, to up to 5 recipients per email and 50 recipients per 24 hours
* **Account:** read-only `account_status`. When `api_wallet.reconciliation_required` is true, wallet figures are provisional and REST requests billed to the API wallet are held for billing review.
* **Feedback:** `send_feedback` tells the StefanBrain team where a tool or endpoint got in your way, and `list_feedback` returns your reports with the team's replies. Both are free. See [Send feedback](#send-feedback).

The web, Amazon, Google Trends, website-traffic, and Reddit tools appear only when their provider is configured. Every tool also accepts an optional `context` string, up to 2,000 characters, recorded in the account owner's audit log.

Not served over MCP: StefanBrain's knowledge corpus (`knowledge_search`, `knowledge_read`; use `ask_stefanbrain` instead), chat-only tools (`chat_constraints`, `share_chat`, `request_connector_connect`, `collect_web_artifacts`, `capture_funnel`), `manage_automations` (change schedules in the app), and the admin-only `generate_hooks`. Direct video analysis, cuts, and transcript tools are also excluded.

Put a hosted video URL in an `ask_stefanbrain` message when the agent should read it. Handshake, `tools/list`, and job status, result, and cancel calls do not count against request limits. Each `stage_file`, `send_feedback`, or `list_feedback` call counts as a request but never draws on the plan pool or wallet. Calls that start work count and draw on the plan pool.

### Poll a job

Submit tools return `runId` and `status: "running"`. Call `get_job_status` with `{"family": "<family>", "run_id": "<runId>"}` plus the `project` and `session` you submitted with. When `status` is `succeeded`, `failed`, or `cancelled`, call `get_job_result`.

| Tool | `family` |
| - | - |
| `create_images`, `edit_image` | `static_ad` |
| `find_angles` | `angle_finder` |
| `review_copy` | `copy_chief` |
| `review_funnel` | `cro_funnel_review` |
| `research_shortform` | `shortform_research` |
| `get_meta_ad` | `meta_ad_lookup` |
| Parked `ask_stefanbrain` | `mcp_ask` |

For a submit-tool job, `get_job_result` returns a `result` of `{kind: "files", summary, files: [{path}]}` for files saved in the job's chat, or `{kind: "inline", summary, data}`. A failed job carries its reason in `error`. Open that chat in StefanBrain, or list its files with `GET /api/developers/v1/files?chat_id=…` and fetch each `download_url`. Find the chat's id with `list_chats`.

### Stage a file inside an MCP client

Use one input lane for each `stage_file` call:

```json theme={"system"}
{
  "url": "https://example.com/brief.pdf"
}
```

Or send local bytes when the client supports base64 tool arguments:

```json theme={"system"}
{
  "base64_data": "JVBERi0xLjQK...",
  "filename": "brief.pdf",
  "content_type": "application/pdf"
}
```

The result includes `upload_id`, filename, content type, byte count, and `expires_at`. Uploads last 24 hours and can be reused across asks.

`stage_file` stages one file per call, up to 100 MB. MCP requests over 150 MB are refused with `413 request_too_large`; host large files and pass `url`.

Audio and video files are rejected by both staging lanes. Host those files and include the URL in the ask message.

## Work in a chat or project

Each tool call runs in a real chat in your account. Clients that support custom headers can pin a default location:

```http theme={"system"}
x-stefanbrain-chat: chat_...
x-stefanbrain-project: proj_...
```

Without either header, StefanBrain uses the account's **Developer API** chat. A project header gives that project its own Developer API chat.

Connector clients that cannot set headers can address work through tool arguments:

* `project` accepts a `proj_...` id or project UUID.
* `session` creates or reuses a `Developer API — <label>` chat inside the resolved project.
* `ask_stefanbrain.chat` accepts a concrete chat id or `new`.
* `get_job_status`, `get_job_result`, and `cancel_job` accept the same `project` and `session` used by the submit call.

Precedence is:

| Scope | First | Then | Default |
| - | - | - | - |
| Project | Tool `project` argument | `x-stefanbrain-project` | No project |
| Chat | `ask_stefanbrain.chat`, then `session` | `x-stefanbrain-chat` | Developer API chat |

There is no session header. Session labels use 1–128 characters from `A–Z`, `a–z`, `0–9`, `_`, `.`, `:`, or `-`.

Repeat the same `project` and `session` when polling a job or retrying an idempotent submit. Lookups for submit-tool jobs are chat-scoped, so a different address cannot find the job. Parked asks (`mcp_ask`) resolve by account, but keep the same address anyway. A poll with a wrong or unused `session` label also creates an empty `Developer API — <label>` chat.

Use `list_projects` to find accessible `proj_...` identifiers. Use `get_project_context` to retrieve a project's instructions, maintained digest, file roster, and relevant indexed excerpts for a query. Pass a roster `file_id` to get a temporary download URL for the original file. When the MCP connection is already pinned to a project, `get_project_context` can omit its project argument and use that pin.

An inaccessible project returns `404 project_not_found`. A chat and project that do not match return `409 chat_project_mismatch`.

A typical coding-assistant flow is:

```text theme={"system"}
list_projects → get_project_context with the current question → complete the grounded task
```

## Send feedback

The server instructions tell your assistant to call `send_feedback` when a StefanBrain tool or endpoint fails, returns the wrong thing, or lacks something it needs. Send one report per problem, with the tool, the error text, and the run or chat id. The StefanBrain team reads every report and replies.

`list_feedback` returns the account's reports, newest first, each with its status and the team's reply. If the team replied to this account's reports in the last 14 days, the server instructions on the next connection end with up to 3 of those replies.

Each account can send 60 reports in any rolling hour. See [Agent feedback](/developers/feedback) for the fields, statuses, and errors.

## Errors

A request-level failure returns an HTTP status and an `error` object with `message`, `type`, and `code`:

| Status | `code` |
| - | - |
| `400` | `invalid_chat` or `invalid_project` from a header; `invalid_project`, `invalid_session`, `conflicting_project`, or `conflicting_session` from tool arguments |
| `401` | `unauthorized`, with a `WWW-Authenticate` header for OAuth discovery |
| `403` | `api_key_scope_forbidden`, `api_key_suspended`, or `developer_access_forbidden` |
| `404` | `project_not_found` or `chat_not_found` |
| `409` | `chat_project_mismatch` or `chat_id_retired` |
| `413` | `request_too_large` |
| `429` | A rate-limit code with `Retry-After`, or `monthly_usage_limit_reached` with `resets_at` |
| `503` | `workspace_busy` or `mcp_unavailable` |

A tool failure returns `isError: true` with the message in `content` and no `structuredContent`. Messages for invalid arguments and unknown tools begin with `MCP error -32602`.

A busy chat is not an error. `ask_stefanbrain` returns `status: "chat_busy"` with `active_run_id` and `hint.suggestion` (`wait_and_retry` or `use_own_chat`).

See [Authentication](/developers/authentication) for key scopes, [Rate limits](/developers/rate-limits) for limit codes, and [Pricing](/developers/pricing) for the difference between MCP and REST billing.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.