Posthell
Social media scheduler for AI agents: draft posts into a human-approved queue for 15 networks.
Should I use this
Quality & Safety
Findings (2)
- LOWin shape_post
- LOWin get_upload_url
Based on automated analysis of tool definitions and protocol compliance.
Context Cost
This is the approximate number of tokens consumed each time the server's tools are loaded into a model's context. Higher counts reduce the attention available for other tasks.
Install
One-Click Install
Add this to your `claude_desktop_config.json` file:
{
"mcpServers": {
"posthell": {
"url": "https://www.posthell.com/api/mcp"
}
}
}Remote endpoints
https://www.posthell.com/api/mcpstreamable-httpWhat it can do
Tool inventory
Tools (6)
🟡shape_post(notes)
Turn rough notes about what the user did (shipped a feature, fixed a bug, hit a number) into 2-3 finished social-post angles in the user's voice. Grounded only in the notes - never invents facts. Uses one AI generation from the user's monthly quota.
Input Schema
{
"type": "object",
"properties": {
"notes": {
"type": "string",
"minLength": 8,
"maxLength": 4000,
"description": "Raw notes about what the user did. Rough is fine."
}
},
"required": [
"notes"
],
"$schema": "http://json-schema.org/draft-07/schema#"
}🟢list_accounts
The user's connected social accounts (id, network, handle), their account groups (a named set, e.g. one business's Instagram + YouTube), and what each network needs: caption limit, whether it needs a photo or video, and the settings it takes. Call this first, then pass a group name or account handles to create_draft or publish_post.
Input Schema
{
"type": "object",
"properties": {},
"$schema": "http://json-schema.org/draft-07/schema#"
}🟡get_upload_url(filename, contentType, size)
Use this to post a file from the user's computer (a video you rendered, a photo). Returns uploadUrl and mediaUrl: PUT the raw file bytes to uploadUrl with its Content-Type header (the curl line in the result does it), then pass mediaUrl in the media list of create_draft or publish_post. Files are stored for good, so a draft keeps its video. Up to 50MB. Types: JPG, PNG, GIF, WebP, MP4, MOV, WebM, or PDF.
Input Schema
{
"type": "object",
"properties": {
"filename": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "The file's name, e.g. reel-week-1.mp4"
},
"contentType": {
"type": "string",
"minLength": 3,
"maxLength": 100,
"description": "MIME type, e.g. video/mp4, video/quicktime, image/jpeg, image/png"
},
"size": {
"type": "integer",
"minimum": 1,
"maximum": 9007199254740991,
"description": "File size in bytes"
}
},
"required": [
"filename",
"contentType",
"size"
],
"$schema": "http://json-schema.org/draft-07/schema#"
}🟡create_draft(body, group, accounts, platforms, media, ...)
Save a post as a DRAFT in the user's posthell queue, with any photos or videos and per-network settings (Instagram Reel cover, YouTube title/tags). Nothing is published by this tool: the user approves it in the dashboard. Optionally propose a publish time. Warnings in the result are what would stop it publishing; fix them by calling again.
Input Schema
{
"type": "object",
"properties": {
"body": {
"description": "The caption. Optional when the post is a photo or video. For YouTube it becomes the description unless overrides.youtube is set.",
"type": "string",
"maxLength": 5000
},
"group": {
"description": "Post to every connected account in this group, by name (e.g. \"Bakery\"). list_accounts shows the groups. Combine with platforms to send to only some of the group's networks.",
"type": "string",
"maxLength": 60
},
"accounts": {
"description": "Post to these accounts, by handle (e.g. \"posthell_bakery\") or id from list_accounts. Default: every connected account (narrowed by platforms).",
"maxItems": 20,
"type": "array",
"items": {
"type": "string",
"maxLength": 120
}
},
"platforms": {
"description": "Only these networks.",
"type": "array",
"items": {
"type": "string",
"enum": [
"x",
"linkedin",
"instagram",
"threads",
"bluesky",
"facebook",
"tiktok",
"youtube",
"pinterest",
"reddit",
"telegram",
"discord",
"googlebusiness",
"whatsapp"
]
}
},
"media": {
"description": "Photos or videos, in order. One video = an Instagram Reel / a YouTube video.",
"maxItems": 10,
"type": "array",
"items": {
"anyOf": [
{
"type": "string",
"maxLength": 2048
},
{
"type": "object",
"properties": {
"url": {
"type": "string",
"maxLength": 2048,
"description": "https URL: a mediaUrl from get_upload_url, or a public file"
},
"type": {
"description": "Read from the file extension when omitted",
"type": "string",
"enum": [
"image",
"video"
]
},
"alt": {
"description": "Alt text describing it, for networks that show it",
"type": "string",
"maxLength": 500
}
},
"required": [
"url"
]
}
]
}
},
"platformOptions": {
"description": "Settings for each network, keyed by network. Only the networks the post goes to are used.",
"type": "object",
"properties": {
"reddit": {
"description": "Reddit options",
"type": "object",
"properties": {
"subreddit": {
"type": "string",
"maxLength": 100,
"description": "Subreddit. Posts go to your default subreddit unless set. Flair-required subs reject posts without a flair."
},
"flairText": {
"type": "string",
"maxLength": 64,
"description": "Flair."
},
"title": {
"type": "string",
"maxLength": 300,
"description": "Title."
},
"nsfw": {
"type": "boolean",
"description": "Mark NSFW."
},
"spoiler": {
"type": "boolean",
"description": "Mark spoiler."
}
}
},
"pinterest": {
"description": "Pinterest options",
"type": "object",
"properties": {
"boardId": {
"type": "string",
"maxLength": 200,
"description": "Board. Pins must go to a board. Pick which one this pin lands on. Omit to use the network's default."
},
"link": {
"type": "string",
"maxLength": 2048,
"description": "Destination link. Where the pin sends people, usually your product or article page. Without it the pin links nowhere. An https URL."
},
"title": {
"type": "string",
"maxLength": 100,
"description": "Pin title. Pinterest is a search engine, so a clear keyword-led title matters more than the caption."
}
}
},
"instagram": {
"description": "Instagram options",
"type": "object",
"properties": {
"contentType": {
"type": "string",
"enum": [
"story"
],
"description": "Post as. A photo or carousel goes to your feed, a video posts as a Reel. Shows for 24 hours, full screen. One photo or video, no caption. Values: \"story\" = Story. Omit for a normal post."
},
"collaborators": {
"maxItems": 3,
"type": "array",
"items": {
"type": "string",
"maxLength": 100
},
"description": "Collaborators. Invite up to 3 Instagram accounts as co-authors, so the post appears on their profile too. They get an invite and the post shows on their profile once they accept."
},
"reelsOnly": {
"type": "boolean",
"description": "Show only in the Reels tab. Leave it off and the Reel also shows on your profile grid. Video posts only."
},
"audioName": {
"type": "string",
"maxLength": 100,
"description": "Audio name. Names the Reel's original sound. Instagram ignores it when the Reel uses licensed music. Video posts only."
},
"coverUrl": {
"type": "string",
"maxLength": 2048,
"description": "Cover image. Video posts only. An https URL."
},
"coverAtMs": {
"type": "integer",
"minimum": 0,
"maximum": 3600000,
"description": "Cover frame (ms). Video posts only."
}
}
},
"facebook": {
"description": "Facebook options",
"type": "object",
"properties": {
"contentType": {
"type": "string",
"enum": [
"reel",
"story"
],
"description": "Post as. A normal post on your Page. One vertical video, shown in Reels. Shows for 24 hours, full screen. One photo or video, no caption. Values: \"reel\" = Reel, \"story\" = Story. Omit for a normal post."
},
"title": {
"type": "string",
"maxLength": 255,
"description": "Reel title. A separate title shown on the Reel, apart from the caption."
}
}
},
"tiktok": {
"description": "TikTok options",
"type": "object",
"properties": {
"privacyLevel": {
"type": "string",
"enum": [
"PUBLIC_TO_EVERYONE",
"MUTUAL_FOLLOW_FRIENDS",
"FOLLOWER_OF_CREATOR",
"SELF_ONLY"
],
"description": "Who can see it. TikTok asks you to choose this for every post. Required for TikTok. It must be one the account allows."
},
"allowComment": {
"type": "boolean",
"description": "Allow comments. Default true."
},
"allowDuet": {
"type": "boolean",
"description": "Allow duets. Video posts only. Default true."
},
"allowStitch": {
"type": "boolean",
"description": "Allow stitches. Video posts only. Default true."
},
"commercialContentType": {
"type": "string",
"enum": [
"none",
"brand_organic",
"brand_content"
],
"description": "Promotional?. TikTok labels promotional posts. Promoting your own business counts. Values: \"none\" = No, \"brand_organic\" = Yes, my own business, \"brand_content\" = Yes, a paid partnership."
},
"videoMadeWithAi": {
"type": "boolean",
"description": "Made with AI. Set for you when the post uses an image made with AI."
},
"autoAddMusic": {
"type": "boolean",
"description": "Let TikTok add music. Photo posts only."
},
"draft": {
"type": "boolean",
"description": "Send to my TikTok inbox instead. It lands in your TikTok inbox so you can add a sound and post it from the app."
}
}
},
"youtube": {
"description": "YouTube options",
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 100,
"description": "Title."
},
"visibility": {
"type": "string",
"enum": [
"public",
"unlisted",
"private"
],
"description": "Visibility."
},
"tags": {
"maxItems": 60,
"type": "array",
"items": {
"type": "string",
"maxLength": 100
},
"description": "Tags. Search words for YouTube, comma-separated. Up to 500 characters in total."
},
"categoryId": {
"type": "string",
"enum": [
"1",
"2",
"10",
"15",
"17",
"19",
"20",
"22",
"23",
"24",
"25",
"26",
"27",
"28",
"29"
],
"description": "Category. Values: \"22\" = People & Blogs, \"26\" = Howto & Style, \"27\" = Education, \"28\" = Science & Technology, \"24\" = Entertainment, \"23\" = Comedy, \"1\" = Film & Animation, \"10\" = Music, \"15\" = Pets & Animals, \"17\" = Sports, \"19\" = Travel & Events, \"20\" = Gaming, \"25\" = News & Politics, \"29\" = Nonprofits & Activism, \"2\" = Autos & Vehicles."
},
"madeForKids": {
"type": "boolean",
"description": "Made for kids."
},
"madeWithAi": {
"type": "boolean",
"description": "Realistic content made with AI. Tick when it shows realistic people, places or events that were made or changed with AI. YouTube adds a label. Animation and graphics don't need it."
},
"coverUrl": {
"type": "string",
"maxLength": 2048,
"description": "Thumbnail image. Video posts only. An https URL."
},
"coverAtMs": {
"type": "integer",
"minimum": 0,
"maximum": 43200000,
"description": "Thumbnail frame (ms). Video posts only."
}
}
}
}
},
"overrides": {
"description": "A caption of its own for a network, used instead of body there. For YouTube this is the video description.",
"type": "object",
"properties": {
"x": {
"type": "string",
"maxLength": 5000
},
"linkedin": {
"type": "string",
"maxLength": 5000
},
"instagram": {
"type": "string",
"maxLength": 5000
},
"threads": {
"type": "string",
"maxLength": 5000
},
"bluesky": {
"type": "string",
"maxLength": 5000
},
"facebook": {
"type": "string",
"maxLength": 5000
},
"tiktok": {
"type": "string",
"maxLength": 5000
},
"youtube": {
"type": "string",
"maxLength": 5000
},
"pinterest": {
"type": "string",
"maxLength": 5000
},
"reddit": {
"type": "string",
"maxLength": 5000
},
"telegram": {
"type": "string",
"maxLength": 5000
},
"discord": {
"type": "string",
"maxLength": 5000
},
"googlebusiness": {
"type": "string",
"maxLength": 5000
},
"whatsapp": {
"type": "string",
"maxLength": 5000
}
}
},
"proposedTime": {
"description": "Suggested publish time, ISO 8601. Only a suggestion - pre-fills the user's scheduler.",
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}🟢list_posts(status, limit)
Read the user's recent posts and drafts with per-network status, the live link once a post is up, and the reason when one failed. Published posts include engagement metrics (likes, comments, shares, engagementRate - impressions on plans with full analytics) once analytics have synced. Use it to avoid duplicating a topic AND to learn which topics performed before drafting the next post.
Input Schema
{
"type": "object",
"properties": {
"status": {
"description": "Filter by status (posted and published are synonyms)",
"type": "string",
"enum": [
"draft",
"scheduled",
"posted",
"published",
"failed",
"partly_posted"
]
},
"limit": {
"description": "Max results (default 15)",
"type": "integer",
"minimum": 1,
"maximum": 50
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}🟢get_growth
The user's follower counts and 7-day growth per connected network, plus which network is growing fastest. Use this to decide where a post matters most or to report progress.
Input Schema
{
"type": "object",
"properties": {},
"$schema": "http://json-schema.org/draft-07/schema#"
}Community
Evidence