---
title: MCP tools
description: Every tool the Schedule & Chill MCP server exposes to AI agents, with parameters and examples.
published: 2026-06-07
updated: 2026-09-14
---

# MCP tools
The MCP server exposes nineteen tools. All operate on the account tied to your API key.

Removing a post is reversible — `cancel_post` and `delete_post` soft-delete, and
`restore_post` brings it back. Publishing is not: `publish_now` and `retry_post` send
immediately.

## schedule_post

Create a post for one or more connected platforms.

`when` is **required and has no default** — omitting it is an error, not an instruction to
publish. Publishing is irreversible, so it is never what you get by leaving a field out.

| Parameter      | Type      | Required | Notes                                                                                                                          |
| -------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `content`      | string    | yes      | Post text, ≤ 10,000 characters.                                                                                                |
| `when`         | string    | **yes**  | `"draft"` (saves only — publishes nothing, exempt from the monthly limit), `"now"` (immediate, irreversible), `"next_slot"`, or a future ISO-8601 timestamp. |
| `platforms`    | string[]  | yes      | One or more of `x`, `linkedin`, `linkedin_page`, `facebook`, `instagram`, `pinterest`, `youtube`, `tiktok`, `bluesky`, `mastodon`. |
| `media_ids`    | integer[] | no       | Media library ids to attach.                                                                                                   |
| `scheduled_at` | string    | no       | **Deprecated**, accepted for one more release — same as passing the timestamp in `when`, and ignored when `when` is given.      |

Returns the new `post_id`, `status`, `platforms`, and `scheduled_at`.

Targeting can also be done by `account_ids` or `group_ids` instead of `platforms` — the
three are mutually exclusive. Per-account caption overrides go in `platform_content`,
and platform-native fields in `platform_options`.

## update_post

Change a draft or scheduled post: its content, schedule, target channels, attached
media, per-account captions, or platform options. Only the fields you pass are changed.

| Parameter          | Type      | Required | Notes                                                              |
| ------------------ | --------- | -------- | ------------------------------------------------------------------ |
| `post_id`          | integer   | yes      | Id of the draft or scheduled post.                                 |
| `content`          | string    | no       | New shared text.                                                   |
| `scheduled_at`     | string    | no       | New publish time, ISO 8601.                                        |
| `account_ids`      | integer[] | no       | Retarget. Mutually exclusive with `group_ids` and `platforms`.     |
| `media_ids`        | integer[] | no       | Replace the attached media.                                        |
| `platform_content` | object[]  | no       | Per-account caption overrides.                                     |
| `platform_options` | object[]  | no       | Per-account native settings, e.g. YouTube `{title, privacy}`.      |

## get_post

Fetch one post: its status, derived outcome, and per-channel publish result including
the error message for any channel that failed. Also finds cancelled and deleted posts,
which come back with a `deleted_at` and an outcome of `deleted`.

| Parameter | Type    | Required | Notes           |
| --------- | ------- | -------- | --------------- |
| `post_id` | integer | yes      | Id of the post. |

## get_accounts

List all connected social accounts and their status. No parameters. Returns each account's `id`, `platform`, `account_name`, `username`, and `is_active`.

## get_groups

List your channel groups and the account ids each contains. No parameters. Use a
`group_id` from here to target a whole group with `schedule_post` or `update_post`.

## get_capabilities

The MCP twin of `GET /api/capabilities`, for agents that only have this server
configured. Returns which platforms are live for you, the exact `platform_options`
schema each accepts (key, type, allowed values, default), per-platform media rules, and
upload size limits.

| Parameter  | Type   | Required | Notes                                                    |
| ---------- | ------ | -------- | -------------------------------------------------------- |
| `platform` | string | no       | Limit to one platform. Omit for everything available.    |

**Call this before passing `platform_options`.** An option key outside the schema is
dropped silently and the call still returns success — so a guessed key publishes the
default instead of what you asked for. A platform with no native options comes back as
an explicit `{}`, never a missing key.

## get_queue_slots

The next open slots in the posting schedule, so you can offer a human real times
("Tue 9:00 AM or Wed 1:00 PM?") before writing anything. Read-only — it reserves nothing.
Pair it with `when: "next_slot"` on `schedule_post`.

| Parameter | Type    | Required | Notes                     |
| --------- | ------- | -------- | ------------------------- |
| `count`   | integer | no       | How many slots to return. |

## list_posts

List posts, newest-scheduled first. Defaults to `status="scheduled"` — the upcoming queue.

| Parameter | Type    | Required | Notes                                                              |
| --------- | ------- | -------- | -------------------------------------------------------------------- |
| `status`  | string  | no       | `scheduled` (default), `draft`, `published`, `failed`, or `all`.    |
| `limit`   | integer | no       | Default 10, max 50.                                                 |

Use `status="draft"` to find posts created with `schedule_post` `when="draft"` — they do
not appear in the default queue view.

## list_scheduled

List upcoming scheduled posts. `list_posts` supersedes this and can filter by status.

| Parameter | Type    | Required | Notes               |
| --------- | ------- | -------- | ------------------- |
| `limit`   | integer | no       | Default 10, max 50. |

## publish_now

Publish an existing draft, scheduled or failed post immediately. Returns straight away —
publishing runs in the background, so call `get_post` afterwards for the per-channel
outcome.

| Parameter | Type    | Required | Notes                                       |
| --------- | ------- | -------- | ------------------------------------------- |
| `post_id` | integer | yes      | Id of a draft, scheduled or failed post.    |

## retry_post

Retry only the channels that failed, leaving channels that already published untouched.
Works on a partial outcome too — a post can be `published` overall while one channel failed.

| Parameter | Type    | Required | Notes                                  |
| --------- | ------- | -------- | -------------------------------------- |
| `post_id` | integer | yes      | Id of a post with one or more failures. |

## duplicate_post

Copy a post's content, first comment, channels and media into a **new draft**. Nothing is
scheduled or sent. Channels disconnected since the original was written are dropped from
the copy and reported in `dropped_account_ids`.

| Parameter | Type    | Required | Notes             |
| --------- | ------- | -------- | ----------------- |
| `post_id` | integer | yes      | Id of the source. |

## cancel_post

Cancel a scheduled post before it publishes. Only posts in the `scheduled` state can be
cancelled — use `delete_post` for any other state. Soft delete: recoverable with
`restore_post`.

| Parameter | Type    | Required | Notes                     |
| --------- | ------- | -------- | ------------------------- |
| `post_id` | integer | yes      | Id of the scheduled post. |

## delete_post

Delete a post in any state. Soft delete, so `restore_post` undoes it, and a deleted post
never publishes. Deleting a post that already published does **not** remove it from the
social platform — only from Schedule & Chill.

| Parameter | Type    | Required | Notes           |
| --------- | ------- | -------- | --------------- |
| `post_id` | integer | yes      | Id of the post. |

## restore_post

Restore a post removed by `delete_post` or `cancel_post`. A post whose scheduled time has
already passed is restored as a **draft** rather than re-armed, so restoring never
publishes anything by itself.

| Parameter | Type    | Required | Notes                   |
| --------- | ------- | -------- | ----------------------- |
| `post_id` | integer | yes      | Id of the deleted post. |

## get_stats

Return posting statistics: total, published, scheduled, draft, and failed post counts, plus media and connected-account totals. No parameters.

## get_analytics

Engagement analytics for published posts over a date window: reach, impressions,
likes/comments/shares, engagement rate, per-channel and per-platform breakdowns, a daily
timeline, and top posts. Defaults to the last 7 days in your own timezone.

| Parameter         | Type      | Required | Notes                                                             |
| ----------------- | --------- | -------- | ----------------------------------------------------------------- |
| `from`            | string    | no       | `YYYY-MM-DD` in your timezone. Default 7 days ago.                |
| `to`              | string    | no       | `YYYY-MM-DD` in your timezone. Default today.                     |
| `account_ids`     | integer[] | no       | Limit to these channels. Omit for all, including disconnected.    |
| `group_id`        | integer   | no       | Limit to a channel group. Ignored when `account_ids` is given.    |
| `top_posts_limit` | integer   | no       | Default 5, max 20.                                                |

Metrics are collected on a schedule after a post goes out, so a post published minutes
ago has none yet. When that happens the response carries a `note` saying so, rather than
letting a fresh post read as zero engagement.

## browse_media

Search and browse the media library. Returns media items with `id`s you can attach to `schedule_post`.

`search` matches the filename, the description (`alt_text`) **and** the tags — so a clip
described as "cable crossover at the gym" is found by `gym` even though its filename is
`PXL_20260806_113344.mp4`.

| Parameter      | Type     | Required | Notes                                                                    |
| -------------- | -------- | -------- | ------------------------------------------------------------------------ |
| `search`       | string   | no       | Matches filename, `alt_text` and tags.                                   |
| `type`         | string   | no       | `image`, `video`, `audio` or `document`.                                 |
| `folder`       | string   | no       | Filter by folder.                                                        |
| `tags`         | string[] | no       | Every tag must be present, e.g. `["lane:bhai","stage:final"]`.           |
| `min_duration` | integer  | no       | Seconds. Items with an unmeasured duration are excluded, not treated as zero. |
| `max_duration` | integer  | no       | Seconds.                                                                 |
| `limit`        | integer  | no       | Default 10, max 50.                                                      |
| `offset`       | integer  | no       | Skip this many, for paging.                                              |

Results carry `alt_text`, `duration` and `sha256` alongside the URL, which is what lets you
tell two clips apart without downloading them. `total` reports how many matched before `limit`
was applied.

## list_asset_vocabulary

Lists the folders and tags actually in use, with counts, plus how many items are untagged.

Takes no parameters. Call it before `browse_media` and before `update_media`: a guessed tag
returns an empty result rather than an error, which reads as "there is no such footage" when
the footage is there under a different name.

## update_media

Set the description, tags or folder on a library item. This is how an asset becomes findable.

| Parameter  | Type     | Required | Notes                                                                        |
| ---------- | -------- | -------- | ---------------------------------------------------------------------------- |
| `media_id` | integer  | yes      | From `browse_media` or `upload_media`.                                       |
| `alt_text` | string   | no       | Human description. Pass `""` to clear.                                       |
| `tags`     | string[] | no       | **Replaces** the whole list, it is not merged. Pass `[]` to clear.           |
| `folder`   | string   | no       | Pass `""` to clear.                                                          |

Only fields you send are changed; omitting one leaves it alone. Tags are lower-cased and
de-duplicated so the vocabulary cannot fork on casing.

## upload_media

Add a media file to the library. Returns a `media_id` to attach to posts.

Two modes via `source`: `url` (default) downloads from a public URL; `local` opens a resumable
chunked session for a file on your machine, with no size cap. `folder`, `tags` and `alt_text`
apply to **both** modes.

| Parameter    | Type     | Required          | Notes                                                          |
| ------------ | -------- | ----------------- | -------------------------------------------------------------- |
| `source`     | string   | no                | `url` (default) or `local`.                                    |
| `url`        | string   | url mode          | Public URL of the file.                                        |
| `filename`   | string   | yes               | Original filename with extension, e.g. `launch.png`.           |
| `mime_type`  | string   | local mode        | e.g. `video/mp4`.                                              |
| `size_bytes` | integer  | local mode        | Exact size in bytes.                                           |
| `sha256`     | string   | no                | Verified at finalize when given.                               |
| `folder`     | string   | no                | Folder to organize the media.                                  |
| `tags`       | string[] | no                | e.g. `["lane:bhai","stage:final"]`.                            |
| `alt_text`   | string   | no                | Description. Set it here — it is what makes the asset findable later. |

Audio (`mp3`, `wav`, `m4a`, `aac`) is accepted as a production input and can never be attached
to a post.
