MCP Tools Reference

View as Markdown

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
platform string No Filter by platform: twitter, linkedin, instagram, threads, tiktok, youtube, facebook, bluesky, mastodon, telegram
fromDate string No Start date (ISO 8601): 2026-02-01T00:00:00Z
toDate string No End date (ISO 8601): 2026-02-28T23:59:59Z
page number No Page number (default: 1)
limit number No Results per page (default: 20, max: 100)
sortBy string No Sort field: createdAt, updatedAt, scheduledTime (default: createdAt)
sortOrder string No Sort direction: asc or desc (default: desc)
responseFormat string No detailed (default) returns full post content; concise truncates each post's content to a preview to save tokens

Example prompts:

"Show my scheduled posts"
"List all posts from last week"
"What LinkedIn posts are scheduled for next month?"
"Show me failed posts"
"List my drafts"

Python example:

import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
 
async def list_scheduled_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()
 
            # List scheduled posts
            result = await session.call_tool("list_posts", {
                "status": "scheduled",
                "limit": 50
            })
            print(result.content[0].text)
 
asyncio.run(list_scheduled_posts())

Response example:

{
  "success": true,
  "posts": [
    {
      "postGroupId": "67a1b2c3d4e5f6a7b8c9d0e1",
      "content": "Excited to share our latest update!",
      "status": "scheduled",
      "scheduledTime": "2026-02-20T14:00:00Z",
      "platforms": [
        {
          "platformId": "linkedin-123456",
          "platform": "linkedin",
          "status": "scheduled"
        }
      ],
      "createdAt": "2026-02-19T10:30:00Z",
      "updatedAt": "2026-02-19T10:30:00Z",
      "mediaUrls": []
    }
  ],
  "pagination": {
    "totalItems": 1,
    "totalPages": 1,
    "page": 1,
    "limit": 20,
    "hasNextPage": false,
    "hasPrevPage": false
  }
}

create_post

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 422 IDEMPOTENCY_KEY_CONFLICT; retrying while the first call is still running returns 409 IDEMPOTENCY_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_urlcomplete_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. coverUrlcoverurl) is rejected with a validation error rather than silently dropped. These six platforms accept settings:

{
  "platformSettings": {
    "instagram": {
      "videoType": "REELS | STORIES",
      "shareToFeed": true,
      "coverUrl": "https://.../cover.jpg"
    },
    "tiktok": {
      "viewerSetting": "PUBLIC_TO_EVERYONE | MUTUAL_FOLLOW_FRIENDS | FOLLOWER_OF_CREATOR | SELF_ONLY",
      "allowComments": true, "allowDuet": true, "allowStitch": true,
      "commercialContent": false, "brandOrganic": false, "brandedContent": false
    },
    "youtube": {
      "privacy": "public | unlisted | private",
      "title": "string", "madeForKids": false,
      "tags": ["string"], "categoryId": "string",
      "playlist": { "id": "string", "platformId": "string" }
    },
    "threads": { "replyControl": "everyone | accounts_you_follow | mentioned_only" },
    "telegram": { "disableNotification": false, "disableWebPagePreview": false, "protectContent": false },
    "linkedin": {
      "repostEnabled": true,
      "repostParentUrn": "urn:li:share:123456",
      "repostVisibility": "PUBLIC | CONNECTIONS"
    }
  }
}

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 asyncio
from datetime import datetime, timedelta
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
 
async 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())

Response example:

{
  "success": true,
  "postGroupId": "67a1b2c3d4e5f6a7b8c9d0e1",
  "scheduledTime": "2026-07-20T09:00:00.000Z"
}

LinkedIn Mentions:

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)

Response example:

{
  "success": true,
  "postGroupId": "67a1b2c3d4e5f6a7b8c9d0e1",
  "status": "scheduled",
  "scheduledTime": "2026-07-20T14:30:00.000Z",
  "platformSettings": {},
  "platforms": ["linkedin-abc123"],
  "posts": [
    {
      "_id": "67a1b2c3d4e5f6a7b8c9d0e2",
      "platform": "linkedin",
      "platformId": "abc123",
      "content": "Excited to share our latest update!",
      "status": "scheduled",
      "postedId": null,
      "permalink": null
    }
  ],
  "media": []
}

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)

Response example:

{
  "success": true,
  "message": "Post updated successfully",
  "scheduledTime": "2026-03-01T15:00:00.000Z",
  "postGroup": {
    "_id": "67a1b2c3d4e5f6a7b8c9d0e1",
    "status": "scheduled",
    "scheduledTime": "2026-03-01T15:00:00Z"
  }
}

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 aiohttp
 
async 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:

  1. Fast path — pass mediaUrls (public https URLs) to create_post/update_post; the server downloads them. No upload steps.
  2. Upload danceget_upload_urlPUT 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())

Response example:

{
  "connections": [
    {
    "platformId": "twitter-123456789",
    "username": "@yourcompany",
    "displayName": "Your Company",
    "profileImageUrl": "https://pbs.twimg.com/profile_images/...",
    "profileUrl": null,
    "tokenStatus": "valid",
    "tokenExpiresIn": null,
    "accessTokenExpiresAt": null,
    "lastSuccessfulPost": null,
    "lastError": null,
    "subscriptionType": "Premium"
    },
    {
    "platformId": "linkedin-Tz9W5i6ZYG",
    "username": "Your Company Page",
    "displayName": "Your Company",
    "profileImageUrl": "https://media.licdn.com/...",
    "profileUrl": "https://www.linkedin.com/company/your-company",
    "tokenStatus": "valid",
    "tokenExpiresIn": "82d 4h",
    "accessTokenExpiresAt": "2026-06-15T12:00:00.000Z",
    "lastSuccessfulPost": "2026-03-10T09:30:00.000Z",
    "lastError": null,
    "subscriptionType": null
    }
  ]
}

Response fields:

Field Type Description
platformId string Unique ID for creating posts (e.g., twitter-123456789)
username string Platform username or handle
displayName string Display name on the platform
profileImageUrl string Profile image URL
profileUrl string/null URL to the profile on the platform
tokenStatus string Token health: valid, expiring_soon, expired, unknown
tokenExpiresIn string/null 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)

Response example:

{
  "success": true,
  "comment": {
    "id": "7434695495614312448",
    "commentUrn": "urn:li:comment:(urn:li:ugcPost:xxx,7434695495614312448)",
    "message": "Great insights! Thanks for sharing."
  }
}

linkedin_delete_comment

Delete a comment from a LinkedIn post.

Parameters:

Parameter Type Required Description
postedId string Yes LinkedIn post URN the comment belongs to
commentId string Yes Comment URN to delete
platformId string Yes Platform connection ID

Example prompts:

"Delete my comment from this post"
"Remove the comment I just posted"

Python example:

async def delete_comment():
    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_delete_comment", {
                "postedId": "urn:li:ugcPost:7429953213384187904",
                "commentId": "urn:li:comment:(urn:li:ugcPost:xxx,7434695495614312448)",
                "platformId": "linkedin-abc123"
            })
            print(result.content[0].text)

LinkedIn Reshare Tool

linkedin_create_reshare

Reshare an existing LinkedIn post to your feed, optionally with commentary.

Parameters:

Parameter Type Required Description
platformId string Yes LinkedIn platform connection ID
parent string Yes Post URN to reshare, such as urn:li:share:123456 or urn:li:ugcPost:123456
commentary string No Commentary shown above the reshare (maximum 3,000 characters)
visibility string No PUBLIC or CONNECTIONS (default: PUBLIC; CONNECTIONS is for personal accounts)

Example prompt:

"Reshare urn:li:share:123456 on linkedin-abc123 with the commentary 'Worth reading'"

LinkedIn Feed Retrieval Tools (Coming Soon — Requires LinkedIn Approval)

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)

Response example:

{
  "success": true,
  "posts": [
    {
      "id": "urn:li:share:7123456789",
      "commentary": "Excited to share our latest product update!",
      "publishedAt": 1709294400000,
      "visibility": "PUBLIC",
      "lifecycleState": "PUBLISHED"
    }
  ],
  "pagination": {
    "start": 0,
    "count": 10,
    "total": 45
  },
  "cached": false
}

linkedin_post_comments

Retrieve comments on a specific 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
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)

Response example:

{
  "success": true,
  "comments": [
    {
      "id": "6636062862760562688",
      "commentUrn": "urn:li:comment:(urn:li:activity:123,6636062862760562688)",
      "actor": "urn:li:person:xxx",
      "message": "Great insights! Thanks for sharing.",
      "created": { "time": 1582160678569 },
      "likesSummary": { "totalLikes": 5 },
      "commentsCount": 2
    }
  ],
  "pagination": {
    "start": 0,
    "count": 20,
    "total": 89
  },
  "cached": false
}

linkedin_post_reactions

Retrieve reactions on a specific 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
start number No Starting index for pagination (default: 0)
count number No Number of reactions to retrieve (default: 50, max: 100)

Example prompts:

"Who liked my LinkedIn post?"
"Show reactions on this post"
"Get all reactions on my announcement"
"List people who reacted to my post"

Python example:

async def get_post_reactions():
    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_reactions", {
                "postedId": "urn:li:ugcPost:7429953213384187904",
                "platformId": "linkedin-abc123",
                "count": 100
            })
            print(result.content[0].text)

Response example:

{
  "success": true,
  "reactions": [
    {
      "id": "urn:li:reaction:(urn:li:person:xxx,urn:li:activity:123)",
      "reactionType": "LIKE",
      "actor": "urn:li:person:xxx",
      "created": { "time": 1686183251857 }
    },
    {
      "id": "urn:li:reaction:(urn:li:person:yyy,urn:li:activity:123)",
      "reactionType": "PRAISE",
      "actor": "urn:li:person:yyy",
      "created": { "time": 1686183300000 }
    }
  ],
  "pagination": {
    "start": 0,
    "count": 50,
    "total": 456
  },
  "cached": false
}

Reaction types returned:

Type Description
LIKE Standard thumbs up
PRAISE Clapping hands
EMPATHY Heart/love
INTEREST Lightbulb (insightful)
APPRECIATION Thank you
ENTERTAINMENT Funny/laughing

Error Handling

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:

Error Message Cause
"API key required..." Missing or invalid API key
"Platform ID not found" Invalid platform connection ID
"Post not found" Invalid post group ID
"API error: 429" Rate limited by the API
"API error: 502" Platform API temporarily unavailable

Example error handling in Python:

try:
    result = await session.call_tool("create_post", {
        "content": "Hello world!",
        "platforms": ["linkedin-abc123"],
        "scheduledTime": "2026-03-01T14:00:00Z"
    })
    print(result.content[0].text)
except Exception as e:
    print(f"Tool error: {e}")

Best Practices

  1. Always get connections first — Use list_connections to get valid platform IDs before creating posts

  2. Use ISO 8601 dates — All dates should be in format 2026-03-01T14:00:00Z

  3. Respect character limits — Check platform limits before creating posts

  4. Handle errors gracefully — Check for error responses in tool results

  5. Batch operations — When posting to multiple platforms, include all platform IDs in a single create_post call