SkedCast

Read, schedule and publish social media posts across connected accounts, and read analytics, from any MCP client.

Community: Submitted by a user or imported; check the owner before granting accessOnlineNo sign-inGlobalFreeCan modify data

What it can do

  • List Scheduled Posts: List the workspace’s composed posts (queue / drafts / published), most recently CREATED first, with optional status / client / platform / free-text filters and a creation-date wi
  • List Post Targets: List individual post DESTINATIONS — one row per platform account a post goes to — with optional status / statuses / client / account / platform / free-text filters and a SCHEDULE-ti
  • Get Capabilities: The per-platform composing rulebook: caption and hashtag limits, whether text-only is allowed, media rules per kind (allowed, whether OUR adapter can publish it, min/max count, accep

What data it sees

Do you need an account

No: the server works without sign-in

Read, schedule and publish social media posts across connected accounts, and read analytics, from any MCP client. Built for agencies managing many clients. Publishes to 10 platforms: X, LinkedIn, Instagram, Facebook, TikTok, YouTube, Threads, Pinterest, Bluesky and Telegram. OAuth 2.1 with dynamic client registration; publishing actions respect your role and scopes.

How to connect

How to connect SkedCast to Claude

The server address to paste into Claude settings:

https://skedcast--hello-qv9y.run.tools

  1. Open Claude (claude.ai or the desktop app).
  2. Go to Settings → Connectors.
  3. Click Add custom connector.
  4. Paste the server address copied below into Remote MCP server URL and click Add.
  5. In a chat, click + → Connectors and switch the new connector on.

Custom connectors are available on Free, Pro, Max, Team and Enterprise plans (Free is limited to one). On Team and Enterprise an organization Owner adds the connector first under Organization settings → Connectors.

Authorization

After you click Add, a sign-in window for the service opens. Sign in with your own account and approve access. Claude never sees your password.

How to connect SkedCast to ChatGPT

The server address to paste into ChatGPT settings:

https://skedcast--hello-qv9y.run.tools

  1. Open ChatGPT in a browser (chatgpt.com). A Plus, Pro, Business, Enterprise or Edu plan is required.
  2. Turn on developer mode once: Settings → Apps → Advanced settings → Developer mode.
  3. Open Settings → Connectors and click Create.
  4. Fill in the form: Name (anything), Description (one line about what the service does), MCP server URL (copy it below).
  5. Under Authentication choose OAuth if the service requires sign-in, otherwise None. Click Create.
  6. In a new chat open + → Apps/Connectors and enable the connector.

OpenAI has renamed this section before (Connectors → Apps/Plugins). If the label differs, search settings for "developer mode". On Business/Enterprise workspaces an admin must allow custom connectors first.

Authorization

On first use ChatGPT opens the service's sign-in window. Sign in and approve access.

How to connect SkedCast to Cursor

The server address to paste into Cursor settings:

https://skedcast--hello-qv9y.run.tools

Fastest: click Open in Cursor below and confirm the prompt.

Manually:

  1. In Cursor open Settings → Cursor Settings → MCP and click Add new global MCP server.
  2. Paste the JSON copied below into ~/.cursor/mcp.json (per project: .cursor/mcp.json in the repo root).
  3. Save the file. The server appears in the MCP list; authorize it there if asked.

Authorization

If the service needs sign-in, an authorize button appears next to the server in the MCP list.

Server tool list (58)

Raw names from tools/list. Only developers need these.

list_scheduled_postsList the workspace’s composed posts (queue / drafts / published), most recently CREATED first, with optional status / client / platform / free-text filters and a creation-date window. Keyset-paginated: pass the returned nextCursor to page. Note the ordering and the from/to window are both on when a post was AUTHORED, not when it publishes — to reason about publish times, read each post’s targets.
list_post_targetsList individual post DESTINATIONS — one row per platform account a post goes to — with optional status / statuses / client / account / platform / free-text filters and a SCHEDULE-time window. This is the tool to ask "what publishes on this date": unlike list_scheduled_posts, from/to and sort=scheduled_at are about when a target GOES OUT, not when its post was authored. The page carries `total` (matching targets) and `totalPosts` (distinct parent posts) alongside `nextCursor`, so a caller can tell a full page from a complete set instead of assuming the default 25 rows are everything. Keyset-paginated: pass the returned nextCursor to page.
get_capabilitiesThe per-platform composing rulebook: caption and hashtag limits, whether text-only is allowed, media rules per kind (allowed, whether OUR adapter can publish it, min/max count, accepted formats, file-size ceiling, duration bounds, aspect-ratio range), post types, native-scheduling window, default cadence, first-comment support, and the `variants[].overrides` keys each platform REQUIRES or accepts. Read this before composing instead of discovering a limit by failing a publish. Omit `platform` for all of them.
get_postGet a single post with its per-platform variants and resolved targets.
list_clientsList the workspace’s client groups (brands / influencers), newest first. Keyset-paginated: pass the returned nextCursor to page.
list_accountsList connected social accounts (with token health), optionally filtered by client / platform. Keyset-paginated: pass the returned nextCursor to page.
get_analytics_overviewGet the analytics overview over an optional date window and client / account filter. Returns `summary` (postsPublished, failed, rateLimited, reach, impressions, clicks, likes, comments, shares, saves, engagementRate as a 0-1 fraction, costUsd, followers, followersGained, followersLost), `summary.audience` when the platform reports profile-level activity (profileViews, accountsEngaged, profileLinkTaps, videoViews, videoWatchTimeMinutes, contentPlays), `platforms[]` (the same totals per platform, plus a daily impressions sparkline), `accounts[]` (per connected channel, with profileStats), and `series[]` (one entry per day). A metric a platform does not expose is omitted or null, never a fabricated 0. Use get_post_analytics for per-post grain.
get_post_analyticsPer-post performance — complements get_analytics_overview with post grain. With postId: that post’s lifetime metrics broken down by target. Without: one keyset page of per-post rows (newest-first) over an optional date window and client / account / platform filter; pass the returned nextCursor to page. Every row carries impressions, reach, likes, comments, shares, saves, clicks, engagementRate and permalink. Where the platform reports them it also carries reactionsByType (Facebook: like/love/wow/haha/sad/angry), clicksByType, profileActivity, navigation (Instagram Stories: forward/back/exit), follows, profileVisits, reposts, quotes, linkClicks, totalInteractions, videoViews, videoCompletions, skipRate, watchTimeMinutes, avgViewDurationSeconds, avgViewPercentage, subscribersGained/Lost, audienceRetention, trafficSources and audienceBreakdown. null means the platform does not report that metric for this post — it never means zero. platformRemovedAt is set when the post was deleted on the platform; its figures are then final.
get_account_analyticsAnalytics for ONE connected channel over an optional date window — the account grain that sits between get_analytics_overview (whole workspace) and get_post_analytics (one post). Returns `totals` and a daily `series`, plus two things available nowhere else: `audience` (profile-level activity the platform reports about the PROFILE rather than any post — profileViews, accountsEngaged, profileLinkTaps, videoViews, videoWatchTimeMinutes, contentPlays), and `demographics` (follower demographics as dimension → label → count, e.g. { country: { US: 120 }, age: { '25-34': 90 } }). demographics is a snapshot of the audience TODAY, not a window aggregate, and Meta suppresses it entirely below 100 followers — absent means not reported, never zero. Use list_accounts first to get a socialAccountId.
list_library_assetsList media assets in the content library (DAM), with optional kind / folder / label / client / status / search filters. Newest-uploaded first. Keyset-paginated: pass the returned nextCursor to page.
get_agencyGet the current workspace (agency) profile — name, plan, app mode, settings, and the caller’s role.
list_reportsList the workspace’s saved analytics report definitions (CSV/PDF), newest first. Keyset-paginated: pass the returned nextCursor to page.
get_best_timesGet the worker-derived best posting times (strongest-first slots), optionally filtered by client / platform.
list_approvalsList the workspace's approval queue (pending and decided requests), newest-first, with optional status / post filters. Each row carries a preview of the gated post.
get_posting_limitsAnti-ban posting limits (cadence): pass socialAccountId for one account’s view (effective daily cap / spacing, used-today, reset instant), or omit it for the whole-tenant overview of every active channel plus the tenant bypass flag.
get_quotaRead your adaptive-quota-pool budget for every scarce channel (today: YouTube uploads) BEFORE planning a batch: guaranteed slice, used, best-effort burst headroom, tomorrow’s guarantee tonight, and the window reset (ISO-8601 UTC; null for rolling windows). Read-only.
list_campaignsList the workspace's campaigns (named post groupings), alphabetically, optionally filtered by client / status. Requires the campaigns.read scope.
create_postCompose a post and fan it out across the selected accounts (or save a draft with saveDraft:true). This creates real outbound content — confirm with the user before calling. Retry-safe: retrying with IDENTICAL arguments replays the original result instead of creating a duplicate post (the idempotency key is derived from the arguments). Requires the posts.compose scope.
validate_postDry-run a compose request and get the per-platform verdict WITHOUT writing anything: same arguments as create_post, same validation gate, no post row, no draft, no idempotency record. Returns `{ valid, platforms[], estimatedCostUsd, verdicts? }`, where each platform carries its own `valid` plus the `issues` that would block it. Use this instead of creating a far-future post and cancelling it. Note it does NOT run cadence or quota admission, so `valid: true` means the SHAPE is acceptable, not that a publish cannot later be deferred. Requires the posts.compose scope.
revise_postChange the CONTENT of an already-scheduled post — caption, media, cover, alt text, per-platform overrides — without disturbing its destinations. Nothing about the fan-out is rewritten: the publisher reads content at publish time, so every destination picks the change up on its own schedule. Send only the fields you are changing; an omitted field is left alone, and `variants` are merged by platform. Destinations and schedule are NOT editable here — use reschedule_post / cancel_post / bulk_post_target_action for those. REFUSES, naming the destinations that blocked it, when any has already published or has been handed to the publisher. If the post was already APPROVED, the edit returns the whole post to review and nothing publishes until it is approved again — the result's `reGated` says so. A write action — confirm with the user before calling. Requires the posts.compose scope.
update_postReplace a DRAFT post in place — its content, media, targets, and schedule. Only draft posts can be updated (a scheduled or published post returns a conflict). This rewrites a real draft — confirm with the user before calling. Requires the posts.compose scope.
import_mediaIngest an external image/video into the content library by URL — the file must already be reachable at a public URL. For bytes you already hold (an image your client just rendered, or a file on the caller’s own machine) use `upload_media` instead; this tool cannot accept file contents. This creates a real library asset — confirm with the user before calling. Requires the media.manage scope.
upload_mediaUpload an image or video from your own machine into the content library, and get back a media id usable as `create_post`'s mediaIds. Three phases: `inline` sends the file base64 in this call (≤18 MiB, works in any client); `start` returns a presigned URL your client PUTs the bytes to directly (any size, needs a client that can make HTTP requests — e.g. a shell); `finish` registers what `start` uploaded. For a file already on the public internet use `import_media` instead. This creates a real library asset — confirm with the user before calling. Requires the media.manage scope.
reschedule_postMove a pending post target to a new time — provide an absolute scheduledAt (ISO-8601 with offset) or a relative shiftMinutes. This changes a real scheduled post — confirm with the user before calling. Requires the posts.compose scope.
cancel_postCancel a still-pending post target (draft / queued / rate-limited / failed) before it publishes; an already-published target cannot be canceled. This stops a real scheduled post — confirm with the user before calling. Requires the posts.compose scope.
bulk_post_target_actionReschedule, retry or cancel up to 5000 post TARGETS in one call — the batch form of reschedule_post / retry_post / cancel_post. `action` is `reschedule` | `retry` | `cancel`. For `reschedule`, pass either `params.scheduledAt` (an absolute instant) or `params.shiftMinutes` (relative), optionally with `params.respectCadence`; a relative shift is all-or-nothing across the batch. Returns `{ jobId, accepted, skipped, skippedReasons? }` — a skipped target is reported, never silently dropped. This changes real scheduled content — confirm with the user before calling. Requires the posts.compose scope.
retry_postRe-queue a failed or rate-limited post target so it publishes again immediately; only a failed / rate-limited target can be retried. This re-publishes a real post — confirm with the user before calling. Requires the posts.compose scope.
set_post_recycleSet, update, or disable a post's evergreen recycle settings (enabled / intervalDays / times / endDate / maxRecycles) — the shared recycle-config contract. Pass enabled:false to pause recycling without losing the settings. This changes how a real post republishes — confirm with the user before calling. Requires the posts.compose scope.
shift_campaignMove a whole campaign's still-pending schedule by a relative shiftMinutes (non-zero, ±1 year) — delegates to the existing reschedule engine and returns the accepted / skipped counts. This moves real scheduled posts in bulk — confirm with the user before calling. Requires the campaigns.manage scope.
decide_approvalApprove, reject, or request changes on a PENDING approval request, with an optional reviewer note. Approving releases the gated post for publishing; an already-decided request returns a conflict. This gates real outbound content — confirm with the user before calling. Requires the approvals.decide scope.
list_webhooksList the workspace's outbound webhook endpoints (url, subscribed events, active flag; signing secret masked), newest-first. Requires the webhooks.read scope.
create_webhookRegister an outbound webhook endpoint to receive HMAC-signed event deliveries (post.scheduled, post.published, post.failed, target.rate_limited, account.disconnected, account.token_expiring, approval.requested, approval.decided, media.ready, report.completed). The signing secret is returned ONCE. This creates a real subscription — confirm with the user before calling. Requires the webhooks.manage scope.
delete_webhookDelete an outbound webhook endpoint by id (its delivery history cascades). Idempotent. This removes a real subscription — confirm with the user before calling. Requires the webhooks.delete scope.
get_webhookRead one webhook endpoint: its URL, subscribed events, active state, consecutive-failure count and, when it has been auto-disabled, when and why. The signing secret is NEVER returned here — it is shown once at creation and once on rotation. Requires the webhooks.read scope.
list_webhook_deliveriesRead the delivery log for one endpoint — each attempt with its event type, response status, error and timing. This is how you find out WHY an endpoint stopped receiving: a 401 from your server, a timeout, or an auto-disable after repeated failures. Keyset-paginated: pass the returned nextCursor to page. Requires the webhooks.read scope.
update_webhookChange an endpoint’s URL, its subscribed event list, or whether it is active. Re-enabling an endpoint that was auto-disabled after repeated failures is done here. The event list REPLACES the previous one — send the full set, not a delta. A write action — confirm with the user before calling. Requires the webhooks.manage scope and the webhooks feature.
test_webhookSend a `webhook.test` delivery to one endpoint and report the outcome, so you can prove an endpoint is reachable and correctly verifying signatures WITHOUT waiting for a real event. This performs a real outbound HTTP request to the customer’s URL — confirm with the user before calling. Requires the webhooks.manage scope and the webhooks feature.
rotate_webhook_secretMint a NEW signing secret for one endpoint and return it. THE SECRET IS SHOWN ONCE and is not readable again — hand it to the user, and do not repeat it in summaries, logs or files. The previous secret keeps verifying for a short overlap so the customer can redeploy without dropping deliveries. A write action — confirm with the user before calling. Requires the webhooks.manage scope and the webhooks feature.
create_clientCreate a new client group (a brand or influencer whose accounts are managed together). This adds a real client to the workspace — confirm with the user before calling. Requires the clients.manage scope.
get_campaignRead one campaign with its detail view — name, description, status, colour, date range, owning client, and its post counts. Requires the campaigns.read scope.
create_campaignCreate a campaign — a named group of posts with an optional client, colour and date range. Posts are added with assign_to_campaign. A write action — confirm with the user before calling. Requires the campaigns.manage scope.
update_campaignChange a campaign’s name, description, status, colour or date range. At least one field is required. A write action — confirm with the user before calling. Requires the campaigns.manage scope.
delete_campaignDelete a campaign. The POSTS in it are not deleted — they lose their campaign grouping and keep their own schedules. A write action — confirm with the user before calling. Requires the campaigns.manage scope.
assign_to_campaignAdd up to 500 existing posts to a campaign. Grouping only — nothing about a post’s content or schedule changes. A write action — confirm with the user before calling. Requires the campaigns.manage scope.
unassign_from_campaignRemove posts from a campaign. Grouping only — the posts themselves and their schedules are untouched. A write action — confirm with the user before calling. Requires the campaigns.manage scope.
get_clientRead one client group in full — its name, type, approval policy, posting rules, brand voice and default timezone. Requires the clients.read scope.
update_clientChange a client’s name, type, approval policy, posting rules, brand voice or default timezone. A write action — confirm with the user before calling. Requires the clients.manage scope.
delete_clientArchive a client group. OWNER ONLY — a Manager holding the clients.manage scope cannot do this. A write action — confirm with the user before calling.
get_post_recycleRead one post’s evergreen re-posting configuration — interval, times, end date, remaining recycles — plus the worker’s bookkeeping (when it last recycled, how many times). Returns null when the post is not enrolled. Requires the posts.read scope.
clear_post_recycleTurn OFF evergreen re-posting for a post. Idempotent — clearing a post that was not enrolled succeeds. Already-scheduled recycled copies are not retracted by this; cancel those with cancel_post or bulk_post_target_action. A write action — confirm with the user before calling. Requires the posts.compose scope.
get_post_targetRead a single fan-out destination in full — its status, scheduled time, platform, account, published permalink and external id, and its failure reason when it failed. Requires the posts.read scope.
get_accountRead one connected social account in full — platform, handle, display name, avatar, status (including whether it needs re-authorisation), owning client and connection metadata. Requires the accounts.read scope.
get_approvalRead one approval request with the gated post’s preview and roll-up, so a reviewer can see WHAT they are deciding on before calling decide_approval. Requires the approvals.read scope.
get_media_assetRead one content-library asset in full — filename, kind, size, dimensions, duration, folder, labels, alt text, pipeline status, and the `publishable` / `ready` flags plus the per-platform readiness map. Requires the media.read scope.
get_client_analyticsOne client group’s analytics — totals, a per-platform breakdown and a time series. A different projection from get_analytics_overview filtered by client: this is the client rollup the console renders. Requires the analytics.read scope.
get_reportRead one report definition with its run history — each run’s status, window, and whether an export is available to download. Requires the reports.read scope.
download_report_exportGet a time-limited download URL for one completed report run’s export file. The URL expires, so fetch it when you are ready to use it rather than storing it. Requires the reports.read scope.
create_connect_inviteMint a shareable self-connect link so a client can connect the requested platforms themselves. Returns the link + expiry; share it with the client. This creates a real invite — confirm with the user before calling. Requires the accounts.connect scope.