# Focal MCP Server

The Focal MCP server connects AI assistants to your Focal workspace over the
[Model Context Protocol](https://modelcontextprotocol.io). Search and browse your media library,
read transcripts and on-screen text, work with boards, collections, comments and workflows, and query ad
performance — directly from Claude, ChatGPT, Cursor or any MCP-compatible client.

## Quick start

- **MCP endpoint:** `https://api.focal.inc/mcp`
- **Authentication:** OAuth 2.1 — you sign in to Focal and approve access in the browser. No API
  key needed.
- **Who can connect:** any member of a Focal organization (guests can't). Your organization admin
  can turn the connector off for the whole organization.
- **What the assistant can see:** exactly what you can see in Focal, optionally narrowed further to
  specific spaces when you approve the connection.


## Before you connect

- **A Focal account** on any plan, with the member role or higher. Guests can't connect.
- **The connector turned on for your organization.** It is on by default; an organization admin
  can turn it off for everyone in *Organization settings → Connector*.
- **A client that supports OAuth.** In Claude, the connector is available on the Pro, Max, Team and
  Enterprise plans; on Team and Enterprise, an Owner adds Focal for the organization first, then
  members connect it. Any other MCP client that supports OAuth works too.


## Server details

| | |
| --- | --- |
| Endpoint | `https://api.focal.inc/mcp` |
| Transport | Streamable HTTP (JSON-RPC over `POST`), stateless |
| Server name | `focal` |
| Authorization server | `https://api.focal.inc` (same origin) |
| Protected resource metadata | `https://api.focal.inc/.well-known/oauth-protected-resource` |
| Authorization server metadata | `https://api.focal.inc/.well-known/oauth-authorization-server` |
| MCP Apps | Supported — interactive media player (`ui://focal/media-player.html`) |

The assistant acts as **you**: every call runs with your own Focal permissions, so it can never
read or change anything you couldn't in the Focal app.


## Authentication

Focal uses OAuth 2.1 with PKCE. Most clients handle this for you — you add the server, a browser
window opens, you sign in to Focal and approve.

### The approval screen

When you connect, Focal shows:

- **The app asking for access** — its name and logo. Apps connecting from Claude are marked as
  such.
- **The permissions it gets** — a summary of what the connection can do. There is nothing to
  untick: what the assistant is actually allowed to do comes from your own Focal permissions (see
  below).
- **Which spaces it can use** — the whole organization, or only the spaces you pick.

The connection belongs to the organization you have selected in Focal when you approve. To connect
a different organization, switch organizations in Focal first and connect again.

### Permissions (scopes)

Every connection is granted all of the scopes below. They don't decide what the assistant may do —
**your own Focal permissions do**. The assistant acts as you, so it can only read and change what
your role lets you read and change in the Focal app. For example:

- If you can only view a board, the assistant can read it but not edit it.
- Workflow tools work only if you are an org admin or space owner.
- Space settings, such as media library custom fields, can only be changed by org admins and space
  owners.

To limit a connection, restrict it to specific spaces when you approve it. To stop it entirely,
revoke it (see [Managing connections](#managing-connections)).

For reference, these are the scopes a connection carries:

| Scope | Allows the assistant to… |
| --- | --- |
| `spaces:read` | Read spaces, members and your account details |
| `spaces:write` | Change space settings, such as media library custom fields |
| `media:read` | Read media, folders, library views, transcripts and on-screen text |
| `media:write` | Update media, folders and library views; upload media |
| `media:delete` | Archive media, folders and library views |
| `boards:read` | Read boards, columns, rows and views |
| `boards:write` | Create and update boards, columns, rows and views |
| `boards:delete` | Archive boards, columns, rows and views |
| `collections:read` | Read collections |
| `collections:write` | Create and update collections |
| `collections:delete` | Archive collections |
| `comments:read` | Read comments on media and board rows |
| `comments:write` | Post, edit and react to comments as you |
| `comments:delete` | Remove your own comments |
| `workflows:read` | Read workflows, their runs and the step catalog |
| `workflows:write` | Create, edit and publish workflows |
| `workflows:run` | Start runs of published workflows, which can send Slack messages and email, upload to ad platforms and call external URLs |
| `workflows:delete` | Archive workflows |
| `metrics:read` | Read ad accounts and ad performance (spend, results, metrics) |

### Sessions and tokens

- Access tokens last **1 hour** and are refreshed automatically.
- A connection stays signed in as long as it's used at least once every **30 days**.

### Managing connections

- **Your connections:** *User settings → MCP* lists every app you've connected, with a button to
  revoke each one. Revoking takes effect immediately.
- **Organization admins:** *Organization settings → Connector* has the switch that enables or
  disables the connector for everyone in the organization. Admins can't see or revoke other
  people's individual connections — each connection is personal.


## Compatible clients

Any client that supports remote MCP servers with OAuth works. Point it at
`https://api.focal.inc/mcp`.

### Claude (claude.ai, Claude Desktop, Claude mobile)

1. Open **Settings → Connectors** and add Focal (or choose **Add custom connector** and paste
   `https://api.focal.inc/mcp`).
2. Click **Connect**, sign in to Focal and approve.

On Claude Team and Enterprise plans an owner may need to add the connector for the organization
first.

### Claude Code

```bash
claude mcp add --transport http focal https://api.focal.inc/mcp
```

Then run `/mcp` inside Claude Code and choose **Authenticate** for `focal`.

### ChatGPT and Codex

- **Codex CLI:**

  ```bash
  codex mcp add focal --url https://api.focal.inc/mcp
  codex mcp login focal
  ```

- **ChatGPT:** add a custom connector with the URL `https://api.focal.inc/mcp` (requires a plan and
  workspace settings that allow custom connectors).

### Cursor, VS Code and other clients

Add Focal as a remote (HTTP) server in the client's MCP config, for example:

```json
{
  "mcpServers": {
    "focal": { "url": "https://api.focal.inc/mcp" }
  }
}
```

Clients that only support local (stdio) servers can use the `mcp-remote` bridge:

```json
{
  "mcpServers": {
    "focal": { "command": "npx", "args": ["-y", "mcp-remote", "https://api.focal.inc/mcp"] }
  }
}
```

> **Add Focal only once per app.** Two entries for the same server in one client (for example two
> `mcp-remote` configs) share one sign-in and can keep logging each other out. Different apps —
> Claude and Codex, say — can be connected at the same time without any problem.


## Core workflow

1. **Find your spaces** — `get_account` shows what the connection can access; `list_spaces`
   returns the `space_id`s that most other tools need.
2. **Find content** — `search_media` for natural-language search across your library (including
   sorting and filtering by ad performance), `list_media` / `list_folders` to browse, or
   `list_boards` → `list_board_columns` → `list_board_tasks` for boards.
3. **Go deeper** — `get_media` for details, `get_media_transcript` and `get_media_ocr` for what's
   said and shown, `query_metrics` for performance, `open_media_player` to view results.
4. **Act** — update metadata, set custom field values, fill board rows, organize collections, or
   leave comments.

Every resource includes an `html_url` — a link straight to that item in the Focal web app.


## Available tools

Read-only tools are marked as such to the client, so assistants can run them without asking for
confirmation; write tools are annotated as writes, and archive tools as destructive.

The [MCP tool reference](/developers/mcp-tools) lists every tool with its title, what it does, the
scope it needs and its parameters, grouped by what it may change. It is generated from the server's
tool registry, so it always matches what the server advertises.

### Fewer tools up front (optional)

For clients with a small tool budget, use `https://api.focal.inc/mcp?tools=search`. The server then
lists only a few core tools (`list_spaces`, `get_account`, `search_media`, `open_media_player`,
`report_feedback`) plus a `search_tools` tool the assistant uses to find the rest on demand.


## Example prompts

Each prompt is followed by the tools the assistant typically uses to answer it.

- "Find our top-spending TikTok videos from last month that feature a product close-up." — uses
  `list_spaces`, `search_media` and `query_metrics`.
- "Summarize what's said in these five ads and tag each with its hook type." — uses
  `get_media_transcript`, `list_media_attributes` and `set_media_attribute_value`.
- "Fill in the *Status* and *Concept* columns on the Q4 creative board for every row that's empty."
  — uses `list_boards`, `list_board_columns`, `list_board_tasks` and `update_board_task`.
- "Make a collection of all approved UGC videos and share the link." — uses `search_media` and
  `create_collection`.
- "What did the team comment on this video, and what changed since?" — uses `get_media` and
  `list_comments`.
- "Compare CTR for our static vs. video creatives across all Meta ad accounts." — uses
  `list_ad_accounts`, `list_metrics` and `query_metrics`.


## Rules for AI agents

These conventions apply across all tools and are also sent to the assistant when it connects.

- **Start with `get_account` or `list_spaces`** to discover the `space_id`s other tools require.
- **Tools take lists.** Anything acting on resources by id takes `ids` (or `items`, when each entry
  carries its own values). Send everything in one call rather than looping.
- **Batch results aren't transactions.** A batch returns `{ items, succeeded, failed }` with `items`
  in request order. A failed entry carries its `index` and a `failure` in place of the resource;
  the successful ones stay applied. Retry only the failed entries, with a fresh `idempotency_key`.
- **Say what you want back.** Where a tool takes `fields`, it's required — there's no "everything"
  default. `id` always comes back.
- **Board rows need `columns` too.** Every tool returning board rows requires a `columns` list (ids
  or names from `list_board_columns`). Pass `[]` for no cell values.
- **"Not found" means "not available to you".** An id that doesn't exist and one you can't access
  return the same error.
- **Use `html_url`** to link the user straight to what you found.
- **`report_feedback`** sends feedback to the Focal team about a missing capability or confusing
  behavior. Use it when the user asks to, not for transient errors.


## Limits

| Limit | Value |
| --- | --- |
| Requests per connection | 600 per minute |
| Requests per organization | 1,200 per minute (all connections combined) |
| Page size | 1–100 items (default 25), cursor-based pagination |
| Items per read batch | 100 (25 for transcript, OCR, download and similar per-item reads) |
| Items per write batch | 25 |
| Upload size | Images 1 GB, video 4 GB |
| Upload completion window | 12 hours |


## Errors and troubleshooting

### How errors come back

Problems with a tool call (not found, missing permission, invalid arguments, conflict) come back as
a normal tool result marked `isError: true`, so the assistant can read the message and adjust:

```json
{ "error": { "kind": "insufficient_scope", "message": "…", "status": 403, "required_scope": "boards:write" } }
```

| `kind` | Meaning |
| --- | --- |
| `not_found` | The item doesn't exist or isn't available to you |
| `insufficient_scope` | The connection lacks a permission, or your Focal role doesn't allow it |
| `invalid_arguments` | The request was malformed |
| `conflict` | Retry later — for example, completing an upload before the file finished uploading |
| `internal` | Unexpected error; includes a `request_id` to share with support |

### Common problems

- **The client keeps asking me to sign in.** Check that Focal is added only once in that client
  (see *Compatible clients*). Remove duplicates, restart the client and connect once. For
  `mcp-remote`, clearing its cached credentials (`~/.mcp-auth`) and reconnecting also helps.
- **"The MCP Connector is disabled for this organization."** An organization admin has turned the
  connector off in *Organization settings → Connector*.
- **I can't approve the connection.** Guests can't connect — ask an admin for member access.
- **The assistant can't find something I can see in Focal.** The connection may be limited to
  certain spaces. Revoke it in *User settings → MCP* and reconnect with access to the space you
  need.
- **A write tool says `insufficient_scope`.** Your Focal role doesn't allow the change — for
  example, editing custom fields needs an org admin or space owner. Ask someone with the right role
  to change your access in Focal. (A connection made before all scopes were granted by default may
  also be missing one: revoke it in *User settings → MCP* and reconnect.)


## Links

- Focal help center: https://help.focal.inc
- Model Context Protocol: https://modelcontextprotocol.io
- Raw markdown (for AI agents): https://help.focal.inc/developers/mcp.md
- REST API reference: https://help.focal.inc/developers/api
- MCP tool reference: https://help.focal.inc/developers/mcp-tools
- Privacy policy: https://focal.inc/privacy
- Support: support@focal.inc, or ask your assistant to send feedback with `report_feedback`.
