BulkPublish logo

BulkPublish

1

Bulk publish and schedule to multiple social media channels at once.

1 skills

Bulk Social Media Publishing and Scheduling

Teaches your agents/ models how to publish and schedule to multiple social media channels at once.

--- name: bulk-social-media-publishing-and-scheduling description: Schedule, publish and analyse social media posts across 15 networks through BulkPublish. Use whenever the user wants to draft, queue, cross-post, retry or measure social content, or asks about their channels, queue or posting quota. --- # Working with BulkPublish BulkPublish schedules and publishes posts to 15 social networks from one place, and reads the numbers back. This skill covers the tools available over MCP, the shape of a post, what each network accepts, and the mistakes that produce a rejected request. Connect the hosted server at `mcp.bulkpublish.com/mcp` over Streamable HTTP. Sign-in is OAuth, so there is no key to paste. To run the server locally instead, start `@bulkpublish/mcp-server` over stdio with an API key from Settings then Developer in the `BULKPUBLISH_API_KEY` environment variable. ## How to behave These are not style preferences. Getting them wrong costs the user real posts on real accounts. 1. **Show a draft before you schedule it, and never publish without being asked.** `create_post` with `status: "draft"` is the safe default and is what the tool does when status is omitted. `publish_post` sends immediately, to a live audience, and cannot be undone from here. 2. **Never invent a channel id.** Call `list_channels` and use what it returns. A guessed id either fails or posts to the wrong account. 3. **Ask before deleting.** `delete_post` on a published post does not retract what already went out. 4. **Adapt per network rather than pasting one text everywhere.** Use `platformContent` for per-channel copy. A 3,000-character LinkedIn post truncated into a tweet reads like a mistake, because it is one. 5. **Check the character limit before writing, not after.** The limits table below is the contract; an over-long post is rejected, not trimmed. 6. **Say what you did.** After creating posts, list what went where and when, so the user can correct you before anything publishes. ## Tools The hosted server exposes 20 tools. The local package exposes 72, adding labels, hashtag groups, templates, calendar notes, review and client-connect links, channel sets, schedules, RSS autopost and quota. **Posts** ``` create_post create a draft or a scheduled post update_post change content, timing, channels or media get_post one post, with its per-channel results list_posts filter by status, channel, label, date, approval, assignee publish_post send now (irreversible) retry_post retry failed destinations delete_post remove a post get_post_metrics per-post numbers once published get_queue_slot the next free time in the queue ``` **Channels** ``` list_channels connected accounts, with ids, platform and health ``` **Media** ``` upload_media from a public URL or an absolute local path create_media_upload start a direct upload finalize_media_upload finish one list_media the library ``` **Analytics** ``` get_analytics totals and per-platform breakdown for a date range ``` **Interactive panels** (render in hosts that support MCP Apps) ``` compose_post view_posts view_channels view_media view_analytics ``` ## Creating a post ``` content post text; optional for media-only posts channels [{ channelId, platform }] from list_channels status "draft" (default) or "scheduled" scheduledAt ISO 8601; required when status is "scheduled" timezone IANA zone, e.g. "America/New_York"; defaults to UTC mediaFileIds [ids] from upload_media labels [label ids] platformContent per-platform text overrides platformSpecific per-platform options postTypeOverrides per-platform post type, e.g. reel instead of feed_photo postFormat "post" (default), "video", "reel", "story", "carousel", "thread" threadParts [{ content, mediaFileIds }]; required when postFormat is "thread", minimum 2 requestApproval true holds a scheduled post for a teammate to approve ``` Notes that catch people out: - **The label field on create and update is `labels`.** The filter on `list_posts` is `labelId`. They are different names on purpose. - **Every thread part is length-checked**, not only the first. An over-long part is rejected naming the part number, the platform and its limit. URLs count as 23 characters on X and Mastodon and their real length elsewhere. - **YouTube and TikTok require video.** Do not include them in an image-only post. - **Instagram defaults to `feed_photo`.** For video, set `postTypeOverrides` to `reel` or `feed_video`, or the post is rejected. ## Scheduling `get_queue_slot` reads only `timezone`, `position` and `excludePostId`. It does not take a channel or a date. `position` is `"next"` for the first free slot or `"end"` for the bottom of the queue. The normal flow is: get a slot, create the post as a draft with that time, show it to the user, then update it to `scheduled` once they agree. ## Post status ``` draft not going anywhere yet scheduled waiting for its time publishing going out now published all destinations succeeded partial some destinations succeeded, some failed failed no destination succeeded processing the network is still handling it (video encoding, for example) ``` `partial` and `processing` are real states and are easy to forget. A post sitting in `processing` is not stuck; a `partial` post needs `retry_post`, which retries only the destinations that failed. Approval is separate from status. `approvalStatus` is `none`, `pending`, `approved` or `rejected`. A `scheduled` post whose approval is `pending` or `rejected` will not publish. ## Networks | Network | Characters | Threads | Post types | |---|---|---|---| | X | 280 | yes | tweet | | Instagram | 2,200 | no | feed_photo, feed_video, reel, story, carousel | | Facebook | 63,206 | no | post, reel, story | | LinkedIn | 3,000 | no | post, multi_image, pdf_carousel, article | | Threads | 500 | yes | text, image, video, carousel | | Bluesky | 300 | yes | post | | Mastodon | 500 | yes | post | | TikTok | 2,200 | no | video, photo_slideshow | | YouTube | 5,000 | no | video, short | | Pinterest | 500 | no | pin, video_pin, carousel | | Google Business | 1,500 | no | standard, event, offer | | Discord | 2,000 | no | post | | Telegram | 4,096 | no | post | | Tumblr | 32,768 | no | post | | Snapchat | 160 | no | story, saved_story, spotlight | The platform key for Google Business is `gmb`, not `google_business`. ## Media `upload_media` takes either a public `url` or an absolute `filePath`, not both. Attach the returned id through `mediaFileIds`. Uploaded media is kept by default. `deleteMediaAfterPublish` defaults to **false**, so files stay in the library after their post goes out. ## Analytics `get_analytics` takes a required `from` and `to` in ISO date form, plus optional `channelIds`, `platforms`, `labelIds`, `postFormat`, `mediaType` and `compare`. Set `compare: true` to get the previous equal-length window back alongside the current one, which is how you answer "is this better than last month". Link clicks are reported separately from platform clicks. Do not add the two together: one visit can appear in both. ## Plan limits `-1` means unlimited, `0` means not available on that plan. | | Free | Pro | Business | |---|---|---|---| | Channels | 3 | 30 | 75 | | Accounts per network | 1 | 2 | 5 | | Posts per day | 3 | 30 | unlimited | | Posts per month | 30 | 900 | unlimited | | Scheduled posts pending | 10 | unlimited | unlimited | | Media storage | 100 MB | 2 GB | 10 GB | | API requests per day | 30 | 5,000 | 50,000 | | API keys | 1 | 5 | 10 | | Repeating schedules | 0 | 10 | unlimited | | Labels | 10 | 50 | unlimited | | Team members | 1 | 3 | 10 | | RSS feeds | 1 | 10 | 50 | | RSS auto-publish | no | yes | yes | Daily limits reset at midnight UTC. Storage is cumulative, so deleting old media frees it. ## Mistakes that produce an error - Using `cronExpression` or `content` on a repeating schedule. The fields are `frequency`, `timeOfDay`, `dayOfWeek`, `dayOfMonth` and `contentTemplate`. `frequency` is `daily`, `weekly`, `biweekly` or `monthly`. - Passing `labelIds` in a create or update body. It is `labels` there. - Passing a channel or a date to `get_queue_slot`. - Writing `google_business` instead of `gmb`. - Treating `partial` as success, or as total failure. It is neither. - Scheduling without `scheduledAt`, or sending `scheduledAt` without setting `status` to `scheduled`. - Assuming a post with `approvalStatus: "pending"` will go out. It will not. ## Recipes **Post one thing, safely** 1. `list_channels` 2. `get_queue_slot` with the user's timezone 3. `create_post` as a draft, with `platformContent` per network 4. Show the user every variant and the time 5. `update_post` to `scheduled` once they agree **Turn an article into a set of posts** 1. Read the article 2. `list_channels` 3. Write one variant per network, respecting each limit in the table above 4. One `create_post` with all channels and `platformContent` 5. Show all variants before scheduling anything **Fix a bad day** 1. `list_posts` with `status: "failed"` and a recent `from` date 2. `get_post` on each to see which destinations failed and why 3. `retry_post` only where the cause looks temporary 4. Tell the user which ones need them to act, such as reconnecting an account **Report on the month** 1. `get_analytics` for the range with `compare: true` 2. `list_posts` with `status: "published"` over the same range 3. `get_post_metrics` on the strongest few 4. Answer with their numbers, naming specific posts