AI agents
MCP server
cancel_post supports scheduled-only preview and snapshot fencing. Use id for one post, or ids, profile, from and to for an atomic scheduled batch. Preview with dryRun: true, inspect the selection, then apply its snapshot. It never retracts published content. Upload responses include media retention terms; GET /v1/media/policy exposes the complete policy.
PostLake runs a hosted MCP server: the open standard that lets AI agents use external tools. Connect once and your agent can publish, schedule and pull analytics across every network, just by you asking in plain language.
Server URL
https://api.postlake.dev/mcpConnect in three steps
- Open your agent's connector settings. In Claude: Settings → Connectors → Add custom connector. ChatGPT, Cursor and others have an equivalent "add MCP server" option.
- Paste the URL above.
- Approve on the PostLake screen. You're sent to a PostLake Authorize screen. Sign in and click Approve. OAuth means you do not need to paste an existing API key into the client. The agent then appears under Agents in your dashboard.
OAuth compatibility
Protocol compatibility and safe requests
- PostLake supports MCP
2026-07-28and legacy2025-11-25,2025-06-18and2025-03-26. Existing clients can keep their initialization flow. Modern clients use per-request metadata andserver/discover. - For modern requests, send matching
MCP-Protocol-Version,Mcp-Methodand, for a tool call,Mcp-Nameheaders. Include protocol version, client information and capabilities inparams._meta. Unsupported versions return-32022; header mismatches return-32020. - Use
application/jsonand a single JSON-RPC message, not a batch or response. Request bodies are limited to 2 MiB. Upload file bytes through the signed upload URL, never inside a tool call. Accepted notifications return202with no body and cannot execute tools. - Both
/mcpand the legacy API-key endpoint/v1/mcppreserve agent identity and publishing guardrails. OAuth-connected agents and restricted API keys cannot mint unrestricted keys. Unrestricted API-key automation retains its existing setup capability. - Tool annotations are conservative hints, not permissions or a promise that a write is safe to retry. Keep the original post ID, snapshot and idempotency key. Unknown or uncertain outcomes must be recovered by reading the receipt or post, not creating it again.
- Native clients can omit
Origin. Browser origins are validated; custom browser integrations need their origin configured. Expired credentials return401with OAuth discovery, while database failures are not reported as invalid credentials.
PostLake supports both registration methods used by MCP clients. Newer clients can identify themselves with a Client ID Metadata Document (CIMD). Clients that still use Dynamic Client Registration (DCR), including current Cursor versions, continue to work. The client chooses automatically from PostLake's OAuth metadata, so you do not need different URLs or setup for each service.
Both paths require PKCE and an exact registered callback. The approval screen shows where the result will be returned before you continue. DCR client names are self-declared, so only approve a connection you started and whose return destination you recognize.
Access tokens last one hour and clients refresh them automatically. Signed channel-connection links created by get_connect_link expire after 30 minutes.
The hosted page is for managing connections, not just reconnecting. Owners can add another account on an already-connected platform, reconnect expired channels, or disconnect accounts. The heading stays "Manage your connections"; reconnect warnings appear beside the affected channels. The link's profile and platform restrictions still apply.
When the target is known, prefer get_connect_link with profile, plus platforms if only one network is needed. Omit profile when the human needs to manage multiple profiles. Unscoped links keep access to all profiles; a profile switcher shows one at a time, starting with the first profile that has a channel needing reconnection within the platform filter (otherwise the first profile). Scoped links show only the chosen profile, with no switcher. After the owner reconnects, call list_social_accounts to confirm the channel is active.
Streamable HTTP clients (Cursor and others that send Accept: application/json, text/event-stream) receive HTTP 401 with a WWW-Authenticate challenge on first connect. That is what opens the host's Connect / login prompt. Directories may still probe initialize and tools/list as plain JSON without a token. A green "connected" status with no login means the client skipped that 401; reconnect so it can complete OAuth.
Per-agent setup
Pick your host for the exact steps. Each connects to the URL above over OAuth, so you do not need to paste an existing API key:
Claude Code
One command, then approve the browser page that opens:
claude mcp add --transport http postlake https://api.postlake.dev/mcpStart a fresh session and run /mcp to confirm postlake is connected.
Claude Desktop
Open Settings → Connectors → Add custom connector, paste the server URL, and approve the browser sign-in. PostLake's tools then appear in any chat.
Cursor
Open Settings → Tools & MCP → New MCP server (this opens mcp.json), merge this in, and save:
{
"mcpServers": { "postlake": { "url": "https://api.postlake.dev/mcp" } }
}Then open Customize → MCPs (or Ctrl+Shift+P → Open MCPs) and click Connect / Needs login. The first handshake returns 401 on purpose so that button appears. Sign in and click Approve once. Cursor Desktop may use its native cursor://anysphere.cursor-mcp/oauth/callback callback or a local loopback callback. PostLake supports both.
For something running unattended, send an API key instead and skip the browser step entirely. The same URL takes either credential:
{
"mcpServers": {
"postlake": {
"url": "https://api.postlake.dev/mcp",
"headers": { "Authorization": "Bearer sk_live_..." }
}
}
}An API key does not expire, so nothing has to refresh it and nobody has to approve anything after the key exists. It still appears as a named identity in Agent Control, where its publishing limits are enforced. An OAuth token lasts an hour and is tied to one client, which is what you want when a person is present.
ChatGPT
In ChatGPT's developer mode / custom connectors, add a new MCP server pointing at the URL above, then approve the sign-in.
Gemini CLI
gemini mcp add postlake https://api.postlake.dev/mcpThen approve the browser page to authorize.
VS Code · GitHub Copilot
In Copilot Agent mode → MCP servers → Add, point it at the URL above and sign in.
Any other MCP host
Point its “add MCP server” action at https://api.postlake.dev/mcp with no Authorization header. OAuth signs you in through your browser. The hosted /mcp endpoint accepts OAuth access tokens only; a normal sk_live_ REST API key does not unlock it.
Clients that cannot complete browser OAuth send Authorization: Bearer sk_live_... to the same https://api.postlake.dev/mcp URL. One endpoint takes either credential. API keys have their own agent identity and owner-set guardrails. (POST /v1/mcp is the same server under an older path and still works, so existing configs need no change.)
The toolset
manage_media lists account-owned uploads and previews irreversible file erasure. Use action=preview_delete with an upload ID, obtain the owner's confirmation, then use action=delete with its snapshot. Drafts and pending publications remain protected. This does not remove social posts, cancel schedules or refund publishing credits. delete_post also accepts dryRun and snapshot for network-specific removal previews. See the removal guide.
Once connected, the agent gets tools for the full social workflow. Responses use a consistent, model-readable format throughout:
| Group | What the agent can do |
|---|---|
| Identity, limits & releases | Check the connected account, agent publishing limits, credit balance, and confirmed product releases before acting. Name the authenticated agent when the owner permits it. whoami, set_my_name, get_credits, get_changelog |
| Profiles & channels | List and manage profiles, inspect channel destinations and shoppable products, find a Facebook Page before mentioning it, read ordinary tagged posts and your own posts, prepare, start, inspect, and end Facebook Page live broadcasts, connect supported credential-based channels, create a human approval link for OAuth channels, or disconnect a channel. list_profiles, create_profile, rename_profile, delete_profile, list_social_accounts, get_social_account, list_account_targets, search_facebook_pages, list_products, check_allowance, list_tagged_posts, list_own_posts, list_events, create_event, create_live_broadcast, start_live_broadcast, list_live_broadcasts, get_live_broadcast, end_live_broadcast, connect_account, get_connect_link, disconnect_account |
| Publish & schedule | Upload media from a public URL or directly from the agent's local disk, preview a timezone-aware cadence, dry-run a post, publish now or later, create a scheduled calendar atomically, confirm an asynchronous publish, save a draft for a person to approve and publish it once they have, inspect history, edit or cancel queued posts, and retract something already live. upload_media, upload_media_batch, preview_cadence, validate_post, create_post, create_scheduled_calendar, get_post, confirm_post, list_posts, edit_post, edit_scheduled_posts_bulk, publish_draft, cancel_post, delete_post |
| Read & respond | Read notifications and comments, mark notifications seen, reply to comments, hide a reply you do not want on your post, delete a comment, engage with posts or people, and update supported social profiles. list_notifications, mark_notifications_seen, read_comments, reply_to_comment, hide_comment, delete_comment, engage, update_profile |
| Discover | Look at the network itself, not just your own corner of it: search public posts by keyword or topic tag, look someone up before replying to them, read what an account has been publishing, and find a place to tag on a post. search_posts, look_up_profile, read_profile_posts, search_places, find_creators |
| Direct messages | Read threads, explicitly mark a handled thread as read, reply, or start a supported conversation from a handle. list_conversations, read_conversation, mark_conversation_read, send_message |
| Platform rules | Read each platform's limits, media rules, options and live creator-level publishing constraints. get_platform_capabilities, get_publish_info |
| Analytics | Per-post and cross-platform performance, to decide what to post next. get_analytics, get_post_analytics. Stored reports need no provider calls. Period and Content modes share profile, channel, network, agent, interface and date filters across totals, charts and breakdowns. Set refresh: true for background collection and progress. sort selects views, likes, comments, engagement, engagementRate, impressions or clicks across the full population before returning up to 20 destinations. Structured results include the full report and content-only averageViews with a measured-destination denominator. Methodology and filters. |
| Automation handoff | Account-level API key callers can create another full-access key for a trusted service. OAuth agents cannot see or call this tool because it would bypass their owner-set limits. create_api_key |
| Safe by default | Idempotent retries mean a retrying agent never double-posts, even at 3am. Reads say which networks they could not reach, so an empty result never quietly means "we did not look". |
set_my_name updates only the authenticated agent's display name. The same name appears in Agent Control and post attribution. The account owner can lock it in Agent Control. It does not rename the API key or OAuth client.
Meta advertising features are deferred. Ad account discovery, Facebook and Instagram branded-partner lookup, and Facebook Partnership Ads permissions and branded-content post discovery are not available through MCP. Calls from cached tool lists return unavailable. Their retained REST endpoints return HTTP 501 with error type not_implemented, code meta_ads_deferred, and retryable: false. Reconnecting cannot enable them. Organic publishing, analytics, ordinary tagged posts, and non-ad branded-content publishing are unchanged.
For Instagram, inspect connectVariant and accountType with get_social_account. Stories support both login routes, subject to Meta account eligibility. Facebook Login Stories require a Business account; Instagram Login does not need a Facebook Page. A Story needs one JPEG or PNG image (PostLake converts PNG automatically) or MP4/MOV video, and platformOptions.instagram.placement: "story" on validate_post and create_post. Older connections may not report accountType; Meta checks eligibility when publishing. The Facebook route also adds discovery, product tagging and Facebook-only comment controls, subject to account eligibility.
Upload a local file
upload_media supports both remote and local assets. Pass url for an existing public asset. For a file on the agent's machine, omit url and pass contentType plus the file's sizeBytes. The tool returns a preallocated med_… id, a five-minute HTTPS PUT target and the exact headers to send.
The agent uploads the bytes with its local HTTP client, then passes that media id to create_post. For example, after upload_media({ "contentType": "video/mp4", "sizeBytes": 123456 }), it runs:
curl --fail-with-body --request PUT "$UPLOAD_URL" \
--header "Authorization: Bearer $UPLOAD_TOKEN" \
--header "Content-Type: video/mp4" \
--upload-file "/absolute/path/video.mp4"No temporary public bucket or base64 in the model context is needed. The signed target is account-bound, expires after five minutes, accepts only the prepared type and size, and cannot be reused after a successful upload. The local path stays on the agent's machine; only the bytes are sent to PostLake.
For a carousel, call upload_media_batch with one item per slide instead of upload_media once per file. Each local item still returns its own five-minute PUT target. Then call validate_post before create_post. TikTok photo posts need JPEG or WebP (max 1080×1920); a PNG is converted to JPEG with a warning. Photo posts also need a title via platformOptions.tiktok.title. When nothing should publish unless every selected network can accept the content, send requireAllTargets: true to create_post.
To schedule "7am in London", pass timezone: "Europe/London" with a naive scheduledAt such as 2026-08-13T07:00:00, or set an account default timezone. The stored fire time is always UTC. A naive datetime without timezone (and without an account default) is refused. edit_post can change text, media, platformOptions, the fire time, and the exact destination list through accounts. Send every account you want to keep; new accounts inherit the scheduled time and are checked before saving. An agent requiring approval must ask the owner to change destinations. If a scheduled channel was disconnected, use targetAccounts to map that target's old acc_… id to the exact reconnected channel. PostLake will never guess between same-platform accounts. list_posts accepts status, account, profile, network, agent, surface, approval, date filters, and literal caption search using q (case-insensitive, up to 200 characters). Filters apply before bounded cursor pagination. Date endpoints are inclusive; pass the returned nextCursor to continue. If delete_post cannot retract a live post, the reason includes the live URL.
For a large calendar, use list_posts with state: "scheduled" and sort: "scheduledAt". Its cursor continues after an earlier scheduled post is cancelled. Use cancel_post with scoped ids, or REST POST /v1/posts/bulk/cancel, for atomic scheduled cancellation. Preview with dryRun: true and apply its full-post snapshot. This is not an atomic replacement with a new calendar.
For campaign edits, edit_scheduled_posts_bulk accepts up to 200 exact scheduled post IDs with text, per-platform overrides, destinations, campaign or asset metadata, or shiftMinutes to move each UTC publish time. Use patch.addAccounts to add connected channels while preserving existing destinations and avoiding duplicates; patch.accounts replaces destinations. Do not combine them. Preview with dryRun: true, inspect each before/after diff, then apply the same patch with the returned snapshot. If any selected post or the requested patch changes, or publishing starts, none are edited. Larger selections require sequential batches, not one globally atomic operation. Storage changes together within each batch; timers are re-armed best-effort and the due-post cron is the backstop. list_posts also filters by exact campaign and targetCount (single or shared). Use create_scheduled_calendar to preview and atomically create up to 200 scheduled posts, then apply with the returned snapshot and an idempotency key.
For a scheduled destination edit, first call get_post and include expectedAccounts with its current target account IDs in order. PostLake rejects stale edits instead of silently removing a newer selection.
Approval keeps the original ID, campaign, metadata and options. Use expectedSnapshot from get_post with publish_draft to fence all reviewed content. Guarded agents cannot approve themselves. Call get_post with idempotencyKey instead of id for read-only receipt recovery. Cancellation does not release the key. See approval identity and recovery.
For a multi-user product, an MCP agent can create one profile per customer with create_profile, mint a profile-scoped handoff with get_connect_link, confirm the result with list_social_accounts, and then publish, read analytics, or manage messages through the same profile. PostLake-managed OAuth is ready by default. Optional customer-owned platform OAuth secrets are intentionally configured only through the secure dashboard, REST API, or SDK so a model is never asked to retain permanent third-party credentials.
What comes back
List and action tools usually answer in compact markdown that your assistant can render
as a real table. Complete records such as get_post and detailed capability
lookups return structured JSON when every field matters. Every result carries the ids
needed for the next call.
list_social_accounts (ids shortened here to fit the page):
| id | profile | platform | handle | status |
| --- | --- | --- | --- | --- |
| acc_29fd5066… | postlake | bluesky | crumblelake.bsky.social | active |
| acc_6fb037b0… | postlake | x | yourbrand | active |
| acc_1f756183… | postlake | youtube | @postlake | active |
3 rowslist_posts gives per-network status on one line, so a partial failure is visible at a glance:
| id | state | when | profile | targets | text |
| --- | --- | --- | --- | --- | --- |
| post_bf85a34c… | published | 2026-08-02 20:26 | postlake | bluesky/crumblelake.bsky.social:published | Release update |
| post_9f8e7d6c… | partial | 2026-08-01 14:12 | postlake | bluesky/crumblelake:published x/yourbrand:failed | Product launch |
2 rowscreate_post tells you what happened per network, and spells out anything that did not go:
post_9f8e7d6c: partial
| platform | state | url |
| --- | --- | --- |
| bluesky | published | https://bsky.app/profile/you/post/3ms4t |
| x | failed | |
x did not publish: Not enough credits for an X post with a link (90 needed).When a tool fails you get the sentence first, then the facts your agent can act on, so it can choose differently instead of retrying the same call:
This agent may post to bluesky, linkedin. Not x.
type: agent_not_permitted
retryable: no, trying again will fail the same way
refused by: the platforms limit your owner set
you may use instead: bluesky, linkedinWhat it looks like in use
Once connected you can just say:
"Post this photo to Instagram and LinkedIn, schedule a follow-up
for Friday at 9am, and tell me which of last week's posts did best."Local media files
An MCP server runs remotely and cannot read a path on your computer directly. For a local image or video, call upload_media with its MIME type and no url. PostLake returns a short-lived authenticated uploadUrl, the required PUT method and headers, and a ready-to-run upload instruction. Your agent can upload the local bytes without making the file public or obtaining your API key. The PUT response contains the med_… id to pass to create_post. For a carousel, use upload_media_batch.
For media already on the web, pass its public url to upload_media as before. PostLake fetches and validates it server-side.
Security & revoking access
Agents authorise over OAuth, so you do not paste an existing API key into the client or a chat. Every connection you authorise is listed under Agents in your dashboard. Click Revoke next to any one to cut off its access immediately; it would need to re-authorise to return.
An approved OAuth connection can use the social workflow tools listed above, but it cannot create an unrestricted account API key. This prevents an agent from escaping the limits its owner set. Only approve clients you trust, and revoke any connection you no longer recognise or need.
Per-agent limits
OAuth connections appear under Agents in the dashboard. You can restrict which profiles and networks each connection may publish to, set a daily post limit, control whether it may spend purchased credit packs, and require your approval before anything publishes. When approval is required, create_post saves a draft even if the agent omits draft: true, and that OAuth agent cannot publish the draft itself. The owner can review and publish it from PostLake or with an account-level API key. Call whoami to see the active policy before planning work. Existing connections keep their current direct-publishing behavior until the owner enables approval.