# MCP overview

The server is named **`0bull Phone Farm`** and is served at:

```
https://0bull.net/mcp
```

It drives the 0bull phone farm on behalf of the authenticated user: managing posting
accounts on TikTok, Instagram and YouTube, publishing videos as submissions, running
account lifecycle actions, controlling the physical farm phones, and ordering or managing
billing for new ones. See [Connecting](/mcp/connecting) to add it to your client, and the
[tool reference](/mcp/tools) for all 24 tools.

## Platforms

Accounts and submissions carry a **`platform`** of `tiktok`, `instagram` or `youtube`, and
it defaults to `tiktok` everywhere: a client written before the parameter existed keeps
working unchanged.

An account id is a **UUID that identifies its own platform**, so the tools that take one
(`get-account-tool`, `delete-account-tool`, `login-account-tool`, `stop-login-tool`,
`logout-account-tool`) never ask which platform it is on. Only `create-account-tool`,
`list-accounts-tool` and `create-submission-tool` take a `platform`, because they have no
id to read it from.

Where the platforms genuinely differ:

| | TikTok | Instagram | YouTube |
|---|---|---|---|
| Required to create | `username`, `password`, `slot` | `handle` | `handle` |
| Credentials | Required | Optional | Optional |
| Caption | Up to 2200 chars, optional | Up to 2200 chars, optional | The Short's **title**, up to 100 chars, **required** |

Instagram and YouTube accounts created **without** credentials are record-only: a handle
somebody signed in on the handset by hand, which the farm holds no way to sign in itself.
They are `active` and post fine, but `login-account-tool` refuses them: there is nothing
to type into a login form. Supply a username and password (at creation, or later) and the
login is queued on the phone like TikTok's.

`logout-account-tool` works on TikTok, Instagram and YouTube. A platform's flow can decline
after it checks the phone, and when it does the account status is left unchanged rather
than reported as signed out.

## Everything phone-touching is asynchronous

This is the single thing that surprises callers, and it follows from what 0bull is: there
is a real phone at the other end, and tapping through an app takes time.

So a tool call that reaches a phone **queues the work and returns straight away**. The
response tells you the work was accepted. It does not tell you the work succeeded.
Concretely:

- `create-account-tool` creates the account row **and queues a login on a physical
  phone**. It returns the account immediately, in `pending_login`. (An Instagram or YouTube
  account created without credentials queues nothing and comes back `active`.)
- `create-submission-tool` creates the submission **and queues a publish**. It returns the
  submission immediately, in `pending`.
- `login-account-tool`, `logout-account-tool`, `run-macro-tool`, and `run-phone-agent-tool`
  only acknowledge the start.

To learn what actually happened, poll `get-account-tool` or `get-submission-tool` until
the status reaches a resting state. A real phone is tapping through a real app; this takes
as long as it takes.

The read tools (`list-*`, `get-*`, `list-phones-tool`, `phone-snapshot-tool`,
`phone-ocr-tool`) and the pure database operations (`delete-*`, `cancel-submission-tool`)
are synchronous and answer immediately. `phone-control-tool` is synchronous too: it is a
direct farmd socket op rather than queued work.

### Account status

Poll `get-account-tool` and read `status`. The vocabulary is the same on all three
platforms:

| Status | Meaning |
|---|---|
| `pending_login` | Login queued, not yet started. |
| `logging_in` | A login is mid-flight on the phone. Do not start another. |
| `needs_verification` | The platform challenged the login; it needs human attention. |
| `active` | Logged in and present on its phone. **The only status that can post.** |
| `failed` | The login failed. `login_error` carries the reason. |
| `logged_out` | Signed out on the phone, on any platform. |

`create-submission-tool` rejects any account that is not `active`.

### Submission status

Poll `get-submission-tool` and read `status`:

| Status | Meaning |
|---|---|
| `scheduled` | Queued for a future run. |
| `pending` | Queued, not yet picked up. |
| `ingesting` | The video is being fetched or prepared. |
| `driving` | The phone is being driven through the platform's app. |
| `published` | Posted. *Terminal.* |
| `drafted` | Saved as a draft on the phone. *Terminal.* |
| `failed` | The run failed. *Terminal.* |
| `cancelled` | Cancelled before finishing. *Terminal.* |

`pending`, `ingesting`, and `driving` are in-flight: `cancel-submission-tool` works on
these, `delete-submission-tool` does not. Once a status is terminal the reverse is true.

## Billing and ordering phones

Four tools cover renting phones and checking on billing: `get-billing-tool`,
`start-rental-tool`, `request-phones-tool`, and `get-billing-request-tool`.

`get-billing-tool` reads the account's rentals: how many phones it has, what each one costs
and when it renews, plus any orders still waiting on an admin to assign a phone. Call it
first, before proposing any change.

`start-rental-tool` opens a Stripe Checkout link for a first order. The link only saves a
card, it charges nothing. The order is placed once checkout completes, and each phone is
charged a full month the moment an admin assigns it. Orders are final: there is nothing to
cancel while one waits.

`request-phones-tool` changes an existing rental's phone count, given either a new total or
a signed adjustment. It normally files the change and returns an approval link, so poll
`get-billing-request-tool` for the answer. If the caller has set a standing allowance in
their MCP settings, an increase within it is applied immediately, and the response says
`applied: true`. Read that field rather than assuming: it means the order was placed, not
that a card was charged. The card is still only charged when an admin assigns the phone.

Both `start-rental-tool` and `request-phones-tool` require `accept_terms: true`. Before
calling either, show the user [0bull.net/terms](https://0bull.net/terms) and
[0bull.net/privacy](https://0bull.net/privacy), and get their agreement to those and to
monthly renewal charges. Without it, both tools refuse.

## Scoping

**Every call is scoped to the caller's own accounts and phone grants.** You see your own
accounts and submissions; you reach a phone only if you hold a grant on its slot.

Accounts and submissions you may not access return `Account not found.` /
`Submission not found.`, the same error as a genuinely missing record, so the tools never
leak the existence of records you cannot see. A "not found" may mean it does not exist, or
it is not yours.

## Rate limit

60 requests per minute, the same ceiling as the REST API. Polling a status is a normal
thing to do, just leave a second or two between calls rather than spinning as fast as the
limit allows. A phone takes far longer than a minute to finish a login or a publish.

## Authentication

The server uses **OAuth**, with the authorization endpoints published alongside it. Your
client discovers them and walks you through authorization in a browser: you never copy a
token. See [Connecting](/mcp/connecting).
