Complete reference for the 14 active Publora MCP tools with parameters, examples, and code snippets. Media can be attached two ways: the fast path (pass public https URLs via mediaUrls on create_post/update_post) or the upload dance (get_upload_url → HTTP PUT → complete_media). Three additional LinkedIn feed-retrieval tools (linkedin_posts, linkedin_post_comments, linkedin_post_reactions) are pending LinkedIn approval of the r_member_social permission — see LinkedIn Feed Retrieval Tools below. LinkedIn analytics and workspace-management features are available via the REST OpenAPI reference, not MCP.
Note: Most tools return the backend API object. list_connections deliberately wraps the backend list as { "connections": [...] } for MCP structured content. list_posts also supports a concise mode that truncates content previews and adds response-format metadata.
Posts Tools
list_posts
List posts with optional filters for status, platform, and date range.
Parameters:
Parameter
Type
Required
Description
status
string
No
Filter by status: draft, scheduled, published, failed, partially_published
Create and schedule a post to one or more platforms.
Parameters:
Parameter
Type
Required
Description
content
string
Yes
Post text content
platforms
string[]
Yes
Array of platform connection IDs (from list_connections)
scheduledTime
string
No
When to publish (ISO 8601): 2026-03-01T14:00:00Z. Omit to create a draft.
mediaUrls
string[]
No
Up to 10 public https image/video URLs, downloaded server-side and attached before validation. Pass together with scheduledTime to attach and schedule in one call.
platformSettings
object
No
Per-platform publishing options (see schema below). Strict — unknown platforms or keys are rejected.
idempotencyKey
string
No
Retry key (min length 1), forwarded as the Idempotency-Key header. Reusing it with the identical request replays the original response without creating another post.
Draft behavior:scheduledTime is optional in both MCP and REST — omit it to create a draft. Publishable media-required platforms (Instagram, TikTok, YouTube) must be created as a draft first (or given media via mediaUrls), then scheduled once media is attached. Pinterest is connect-only: passing media may satisfy registry validation, but it cannot be published because scheduler dispatch is not implemented.
Use idempotencyKey whenever a retry is possible. An agent that retries after a network timeout has no way to know whether the first create_post landed — without a key it creates a second post. Pass a fresh unique key (e.g. a UUID) per distinct post; on retry, resend the same key with the same arguments and Publora replays the original result instead of posting again. Reusing a key with different arguments returns 422IDEMPOTENCY_KEY_CONFLICT; retrying while the first call is still running returns 409IDEMPOTENCY_IN_FLIGHT (wait and retry the identical call — do not switch keys).
A scheduledTime in the past is not taken literally. Under 5 minutes late it is always clamped with SCHEDULED_TIME_COERCED. At 5+ minutes it is scheduled to become 400 SCHEDULED_TIME_IN_PAST on 2026-08-25, unless production configuration overrides the date either way.
Publishable media-required platforms (Instagram, TikTok, YouTube): scheduling one of these with no media fails validation with MEDIA_REQUIRED (HTTP 400, { "error": "Validation failed", "validation": {…} }; the error's suggestions name the exact recovery tool calls). To satisfy it: pass mediaUrls in the same create_post call, or create a draft (omit scheduledTime), attach with get_upload_url → complete_media, then update_post with status: "scheduled". Do not schedule Pinterest; it is connect-only.
platformSettings via MCP — supported on create_post and update_post. The schema is strict: a mistyped platform or key (e.g. coverUrl → coverurl) is rejected with a validation error rather than silently dropped. These six platforms accept settings:
For LinkedIn repost settings, CONNECTIONS is personal-profile-only. A company-page repost must use PUBLIC or scheduling returns 400.
YouTube custom thumbnails are not settable here (they need the separate multipart thumbnail endpoint, which MCP does not expose).
Example prompts:
"Schedule 'Hello world!' to LinkedIn for tomorrow at 9am""Post 'We're hiring!' to Twitter and LinkedIn right now""Schedule this announcement to all my accounts for Monday"
Python example:
import asynciofrom datetime import datetime, timedeltafrom mcp import ClientSessionfrom mcp.client.streamable_http import streamablehttp_clientasync def schedule_post(): headers = {"Authorization": "Bearer sk_YOUR_API_KEY"} async with streamablehttp_client("https://mcp.publora.com", headers=headers) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() # First get connections to find platform IDs connections = await session.call_tool("list_connections", {}) # Schedule a post for tomorrow at 9am UTC tomorrow_9am = (datetime.utcnow() + timedelta(days=1)).replace( hour=9, minute=0, second=0, microsecond=0 ).isoformat() + "Z" result = await session.call_tool("create_post", { "content": "Excited to share our latest product update!", "platforms": ["linkedin-abc123"], "scheduledTime": tomorrow_9am }) print(result.content[0].text)asyncio.run(schedule_post())
You can @mention people and companies in LinkedIn posts using this syntax in your content:
@{urn:li:person:ACoAABcD1234EfG|Serge Bulaev} # Mention a person@{urn:li:organization:107107343|Acme Corp Inc} # Mention a company
Example:
"Great insights from @{urn:li:person:ACoAABcD1234EfG|Serge Bulaev} at @{urn:li:organization:107107343|Creative Content Crafts Inc}!"
Important: The display name must exactly match the LinkedIn profile name (case-sensitive), including company suffixes like "Inc", "LLC", etc.
Platform limits:
Platform
Characters
Images
Video
Special Features
LinkedIn
3,000
10
500MB
Documents, @mentions
X/Twitter
280 (25K premium)
4
140s
Auto-threading
Instagram
2,200
10
900s Reels, 3600s feed, 60s carousel
Reels & Stories supported
Threads
500 (10,000 with text attachment)
20
5min / 1 GB
Threading disabled
TikTok
2,200
35
10min / 4 GB
Image carousel or video
YouTube
5,000 desc
0
12h / 256 GB
Shorts support
Facebook
63,206
10
45min / 2 GB
Page posts, Reels
Bluesky
300
4
3 min / 100 MB
Auto-facet detection
Mastodon
500*
4
~99 MB
Instance-variable
Telegram
4,096 (1,024 captions)
10
24h / 50 MB
Automatic Markdown parsing
*Varies by instance
Note: LinkedIn's "10 images" is the multi-image upload limit, not a carousel. Organic carousels on LinkedIn are not supported via the API (carousels are only available for sponsored/ad content). To share multi-page content organically, use LinkedIn document (PDF) posts instead.
Note: Multi-threaded nested posts on Threads are temporarily unavailable. Single posts and carousel posts to Threads continue to work normally.
get_post
Get details of a specific post group.
Parameters:
Parameter
Type
Required
Description
postGroupId
string
Yes
Post group ID (e.g., 67a1b2c3d4e5f6a7b8c9d0e1)
Example prompts:
"Show me the details of post 67a1b2c3d4e5f6a7b8c9d0e1""What's the status of my last scheduled post?""Get info about my recent post"
Python example:
async def get_post_details(): headers = {"Authorization": "Bearer sk_YOUR_API_KEY"} async with streamablehttp_client("https://mcp.publora.com", headers=headers) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool("get_post", { "postGroupId": "67a1b2c3d4e5f6a7b8c9d0e1" }) print(result.content[0].text)
Note:get_post returns group-level status, scheduledTime, platformSettings, platforms, and media[], plus one posts[] entry per platform target. Each post includes nullable platformId, postedId, and permalink; internal thread-part IDs are not exposed.
update_post
Reschedule or change post status.
Parameters:
Parameter
Type
Required
Description
postGroupId
string
Yes
Post group ID
status
string
No
New status: draft or scheduled
scheduledTime
string
No
New scheduled time (ISO 8601)
mediaUrls
string[]
No
Public https URLs (≤10) to download and append to the post's media.
platformSettings
object
No
Per-platform options to merge (same strict schema as create_post).
idempotencyKey
string
No
Retry key (min length 1), forwarded as the Idempotency-Key header. Reusing it with the identical request prevents repeated media appends and replays the original response.
Note: Provide at least one of status, scheduledTime, mediaUrls, or platformSettings. update_post is not idempotent by default — repeating a call with mediaUrls appends the same media a second time. Pass idempotencyKey to make a retry safe: the repeated call replays the original response instead of appending again.
scheduledTime handling: omitting it keeps the current time. Under 5 minutes late is always clamped with SCHEDULED_TIME_COERCED; 5+ minutes is scheduled to become strict on 2026-08-25 unless production configuration overrides the date either way.
Known discrepancy: the update_post tool description returned by the live MCP server still says a past scheduledTime "is snapped to now". That is only true under the conditions above; the note in this section is authoritative.
Example prompts:
"Reschedule post 67a1b2c3d4e5f6a7b8c9d0e1 to Friday at 3pm""Change my draft to scheduled""Move tomorrow's post to next week""Update the post to publish at 10am instead"
Python example:
async def reschedule_post(): headers = {"Authorization": "Bearer sk_YOUR_API_KEY"} async with streamablehttp_client("https://mcp.publora.com", headers=headers) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool("update_post", { "postGroupId": "67a1b2c3d4e5f6a7b8c9d0e1", "scheduledTime": "2026-03-01T15:00:00Z" }) print(result.content[0].text)
Successful responses may carry warnings. When present, warnings is an array of { code, message, ... } objects (e.g. SCHEDULED_TIME_COERCED, which also carries requested and effective). The key is omitted entirely when there are no warnings. Surface these to the user — the call succeeded, but the API changed something about the request. Full list: Error Handling.
Note: Top-level scheduledTime is always present and is null for drafts. The nested postGroup.scheduledTime is conditional and appears only when a scheduled time is set.
Note: Only posts with status draft or scheduled can be updated. Attempting to update a post in any other status (e.g., published, failed, partially_published) returns a 400 error: "Cannot update post: post is currently in {status} status".
delete_post
Delete a post from all platforms.
Parameters:
Parameter
Type
Required
Description
postGroupId
string
Yes
Post group ID to delete
Example prompts:
"Delete post 67a1b2c3d4e5f6a7b8c9d0e1""Cancel my scheduled post for tomorrow""Remove all my draft posts"
Python example:
async def delete_post(): headers = {"Authorization": "Bearer sk_YOUR_API_KEY"} async with streamablehttp_client("https://mcp.publora.com", headers=headers) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool("delete_post", { "postGroupId": "67a1b2c3d4e5f6a7b8c9d0e1" }) print(result.content[0].text)
Response example:
{ "success": true}
get_upload_url
Get a presigned URL to upload media files.
Parameters:
Parameter
Type
Required
Description
postGroupId
string
Yes
Post group ID to attach media to
fileName
string
Yes
File name (e.g., photo.jpg)
contentType
string
Yes
MIME type (e.g., image/jpeg, video/mp4)
type
string
Yes
Media type: image or video
Upload-layer acceptance:
Type
Formats
Images
The MCP tool accepts an image/* MIME string; scheduling applies the target platform's format allowlist
Videos
The MCP tool accepts a video/* MIME string; scheduling applies the target platform's format allowlist
⚠ Attaching media demotes a scheduled post to draft. Calling get_upload_url on an already-scheduled post demotes it back to draft (postGroupDemoted: true in the response). Attach media on a draft, then schedule with update_post. Companion media tools: complete_media (finalize/validate an uploaded mediaId — optional, the scheduling gate probes lazily) and delete_media (remove one attached media file; also demotes a scheduled post). Media attached via mediaUrls needs neither.
Python example:
import aiohttpasync def upload_image_to_post(): headers = {"Authorization": "Bearer sk_YOUR_API_KEY"} async with streamablehttp_client("https://mcp.publora.com", headers=headers) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() # Get upload URL result = await session.call_tool("get_upload_url", { "postGroupId": "67a1b2c3d4e5f6a7b8c9d0e1", "fileName": "product-photo.jpg", "contentType": "image/jpeg", "type": "image" }) # Parse the response - contains uploadUrl, fileUrl, and mediaId # Response: { "success": true, "uploadUrl": "https://...", "fileUrl": "https://...", "mediaId": "..." } import json data = json.loads(result.content[0].text) upload_url = data["uploadUrl"] # Upload file using the presigned URL async with aiohttp.ClientSession() as http: with open("product-photo.jpg", "rb") as f: await http.put(upload_url, data=f.read())
complete_media
Finalize a file uploaded via get_upload_url (probes the object, persists type/metadata). Call it after the presigned PUT succeeds. (Optional — scheduling also finalizes pending media — but calling it early surfaces format/probe errors before publish.) Not needed for media attached via mediaUrls.
Parameters:
Parameter
Type
Required
Description
mediaId
string
Yes
The mediaId returned by get_upload_url.
delete_media
Remove a media slot from a post (detaches and deletes the underlying file). Deleting media from a scheduled post demotes it to draft — re-schedule with update_post afterward.
Parameters:
Parameter
Type
Required
Description
mediaId
string
Yes
The mediaId of the slot to remove (see the media array in get_post).
Two ways to attach media:
Fast path — pass mediaUrls (public https URLs) to create_post/update_post; the server downloads them. No upload steps.
Upload dance — get_upload_url → PUT the bytes to the presigned URL → complete_media. Use delete_media to drop a slot.
Connections Tool
list_connections
List all connected social media accounts.
Parameters: None
Example prompts:
"Show my connected accounts""What platforms am I connected to?""List my social media accounts""Which accounts do I have linked?"
Python example:
async def list_connections(): headers = {"Authorization": "Bearer sk_YOUR_API_KEY"} async with streamablehttp_client("https://mcp.publora.com", headers=headers) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool("list_connections", {}) print(result.content[0].text)asyncio.run(list_connections())
Human-readable time until expiration (e.g., "7d 3h")
accessTokenExpiresAt
string/null
ISO 8601 timestamp when the access token expires
lastSuccessfulPost
string/null
ISO 8601 timestamp of the last successful post via this connection
lastError
object/null
Last error details: { message: string, occurredAt: string }
subscriptionType
string/null
Detected platform subscription tier when available (used for X Premium/PremiumPlus limits)
LinkedIn Reactions
linkedin_create_reaction
React to a LinkedIn post.
Parameters:
Parameter
Type
Required
Description
postedId
string
Yes
LinkedIn post URN
platformId
string
Yes
Platform connection ID
reactionType
string
Yes
Reaction type (see below)
Reaction types:
Type
Description
LIKE
Standard like
PRAISE
Clapping hands
EMPATHY
Heart/love
INTEREST
Lightbulb/insightful
APPRECIATION
Thank you
ENTERTAINMENT
Funny/laughing
Example prompts:
"Like this LinkedIn post""React with PRAISE to post xyz""Add a heart reaction to my colleague's post"
Python example:
async def react_to_post(): headers = {"Authorization": "Bearer sk_YOUR_API_KEY"} async with streamablehttp_client("https://mcp.publora.com", headers=headers) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool("linkedin_create_reaction", { "postedId": "urn:li:share:7123456789", "platformId": "linkedin-abc123", "reactionType": "LIKE" }) print(result.content[0].text)
linkedin_delete_reaction
Remove a reaction from a LinkedIn post.
Parameters:
Parameter
Type
Required
Description
postedId
string
Yes
LinkedIn post URN
platformId
string
Yes
Platform connection ID
Example prompts:
"Remove my reaction from this post""Unlike the LinkedIn post"
LinkedIn Comment Tools
linkedin_create_comment
Post a comment on a LinkedIn post.
Parameters:
Parameter
Type
Required
Description
postedId
string
Yes
LinkedIn post URN (e.g., urn:li:share:123456 or urn:li:ugcPost:123456)
platformId
string
Yes
Platform connection ID
message
string
Yes
Raw input up to 10,000 characters; after mention processing, the text sent to LinkedIn must be at most 1,250 characters. Supports mentions: @{urn:li:person:ID|Name} or @{urn:li:organization:ID|Company}
parentComment
string
No
Parent comment URN for nested replies
Example prompts:
"Comment 'Great insights!' on this LinkedIn post""Post a comment on my latest LinkedIn update""Reply to this comment with 'Thanks for sharing!'""Comment on this post mentioning @{urn:li:person:ACoAABcD1234EfG|Jane Smith}"
Python example:
async def comment_on_post(): headers = {"Authorization": "Bearer sk_YOUR_API_KEY"} async with streamablehttp_client("https://mcp.publora.com", headers=headers) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool("linkedin_create_comment", { "postedId": "urn:li:ugcPost:7429953213384187904", "platformId": "linkedin-abc123", "message": "Great insights! Thanks for sharing." }) print(result.content[0].text)
Status: DISABLED - These tools are not yet available. They require the r_member_social permission, which is RESTRICTED and requires LinkedIn approval. The implementation is ready and will be enabled once LinkedIn approves the permission for Publora.
linkedin_posts
Retrieve posts authored by a LinkedIn account.
Parameters:
Parameter
Type
Required
Description
platformId
string
Yes
Platform connection ID (e.g., linkedin-XxxYyy)
start
number
No
Starting index for pagination (default: 0)
count
number
No
Number of posts to retrieve (default: 10, max: 100)
sortBy
string
No
Sort order: LAST_MODIFIED or CREATED (default: LAST_MODIFIED)
Example prompts:
"Show my recent LinkedIn posts""Get my last 10 LinkedIn posts""What have I posted on LinkedIn this month?""List my LinkedIn content"
Python example:
async def get_my_linkedin_posts(): headers = {"Authorization": "Bearer sk_YOUR_API_KEY"} async with streamablehttp_client("https://mcp.publora.com", headers=headers) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool("linkedin_posts", { "platformId": "linkedin-abc123", "count": 20, "sortBy": "LAST_MODIFIED" }) print(result.content[0].text)
LinkedIn post URN (e.g., urn:li:share:123456 or urn:li:ugcPost:123456)
platformId
string
Yes
Platform connection ID
start
number
No
Starting index for pagination (default: 0)
count
number
No
Number of comments to retrieve (default: 20, max: 100)
Example prompts:
"Show comments on my last LinkedIn post""Get all comments on this LinkedIn post""What are people saying about my post?""List comments on post urn:li:share:123456"
Python example:
async def get_post_comments(): headers = {"Authorization": "Bearer sk_YOUR_API_KEY"} async with streamablehttp_client("https://mcp.publora.com", headers=headers) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool("linkedin_post_comments", { "postedId": "urn:li:ugcPost:7429953213384187904", "platformId": "linkedin-abc123", "count": 50 }) print(result.content[0].text)
Every successful tool result carries both a text block and a machine-readable structuredContent object (MCP spec). On failure the tool surfaces the backend's full structured payload — code, platform, and suggestions where available — not just a bare message. Common errors include: